rhc 1.27.4 → 1.28.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -271,6 +271,10 @@ module RHC::Rest::Mock
271
271
  stub_api_request(:get, 'broker/rest/cartridges', with_auth).to_return(simple_carts)
272
272
  end
273
273
 
274
+ def stub_simple_regions(empty=false, with_auth=mock_user_auth)
275
+ stub_api_request(:get, 'broker/rest/regions', with_auth).to_return(simple_regions(empty))
276
+ end
277
+
274
278
  def define_exceptional_test_on_wizard
275
279
  RHC::Wizard.module_eval <<-EOM
276
280
  private
@@ -322,6 +326,19 @@ module RHC::Rest::Mock
322
326
  }.to_json
323
327
  }
324
328
  end
329
+
330
+ def simple_regions(empty=false)
331
+ {
332
+ :body => {
333
+ :type => 'regions',
334
+ :data => empty ? [] : [
335
+ {:id => 'region0001', :default => false, :name => 'north', :description => 'Servers in the north of US', :zones => [{:name => 'west', :created_at => '2014-01-01T01:00:00Z', :updated_at => '2014-01-01T01:00:00Z'}, {:name => 'east', :created_at => '2014-01-01T01:00:00Z', :updated_at => '2014-01-01T01:00:00Z'}]},
336
+ {:id => 'region0002', :default => true, :name => 'south', :zones => [{:name => 'east', :created_at => '2014-01-01T01:00:00Z', :updated_at => '2014-01-01T01:00:00Z'}]},
337
+ ],
338
+ }.to_json
339
+ }
340
+ end
341
+
325
342
  def simple_user(login)
326
343
  {
327
344
  :body => {
@@ -435,6 +452,7 @@ module RHC::Rest::Mock
435
452
  ['LIST_DOMAINS', "broker/rest/domains", 'GET'],
436
453
  ['ADD_DOMAIN', "broker/rest/domains", 'POST', ({'optional_params' => [{'name' => 'allowed_gear_sizes'}]} if example_allows_gear_sizes?)].compact,
437
454
  ['LIST_CARTRIDGES', "broker/rest/cartridges", 'GET'],
455
+ ['LIST_REGIONS', "broker/rest/regions", 'GET'],
438
456
  ])
439
457
  end
440
458
 
@@ -636,6 +654,14 @@ module RHC::Rest::Mock
636
654
  raise RHC::Rest::ApplicationNotFoundException.new("Application #{name} does not exist")
637
655
  end
638
656
 
657
+ def find_application_gear_groups_endpoints(domain, name, options = {})
658
+ find_domain(domain).applications.each do |app|
659
+ return app.gear_groups if app.name.downcase == name.downcase
660
+ end
661
+
662
+ raise RHC::Rest::ApplicationNotFoundException.new("Application #{name} does not exist")
663
+ end
664
+
639
665
  def find_application_gear_groups(domain, name, options = {})
640
666
  find_domain(domain).applications.each do |app|
641
667
  return app.gear_groups if app.name.downcase == name.downcase
@@ -844,6 +870,7 @@ module RHC::Rest::Mock
844
870
  if scale
845
871
  @scalable = true
846
872
  end
873
+ @region = nil
847
874
  self.attributes = {:links => mock_response_links(mock_app_links('mock_domain_0', 'mock_app_0')), :messages => []}
848
875
  self.gear_count = 5
849
876
  types = Array(type)
@@ -995,6 +1022,10 @@ module RHC::Rest::Mock
995
1022
  @keep_deployments
996
1023
  end
997
1024
 
1025
+ def region
1026
+ @region
1027
+ end
1028
+
998
1029
  def deployments
999
1030
  base_time1 = Time.local(2000,1,1,1,0,0).strftime('%Y-%m-%dT%H:%M:%S%z')
1000
1031
  base_time2 = Time.local(2000,1,1,2,0,0).strftime('%Y-%m-%dT%H:%M:%S%z')
@@ -0,0 +1,25 @@
1
+ module RHC::Rest
2
+ class Region < Base
3
+ define_attr :id, :name, :description, :default
4
+
5
+ def default?
6
+ !!default
7
+ end
8
+
9
+ def uuid
10
+ client.api_version_negotiated >= 1.6 ? attributes['id'] : attributes['uuid']
11
+ end
12
+
13
+ def zones
14
+ @zones ||= attributes['zones'].map{|z| z['name']}.sort
15
+ end
16
+
17
+ def <=>(other)
18
+ return self.name <=> other.name
19
+ end
20
+
21
+ def to_s
22
+ self.name
23
+ end
24
+ end
25
+ end
@@ -8,7 +8,7 @@ module RHC
8
8
  include RHC::ServerHelpers
9
9
  attr_accessor :hostname, :nickname, :login
10
10
  attr_accessor :use_authorization_tokens, :insecure, :timeout
11
- attr_accessor :ssl_version, :ssl_client_cert_file, :ssl_ca_file
11
+ attr_accessor :ssl_version, :ssl_client_cert_file, :ssl_client_key_file, :ssl_ca_file
12
12
  attr_accessor :default
13
13
 
14
14
  def self.from_yaml_hash(hash)
@@ -23,8 +23,9 @@ module RHC
23
23
  @use_authorization_tokens = RHC::Helpers.to_boolean(args[:use_authorization_tokens], true)
24
24
  @insecure = RHC::Helpers.to_boolean(args[:insecure], true)
25
25
  @timeout = Integer(args[:timeout]) if args[:timeout].present?
26
- @ssl_version = args[:ssl_version]
26
+ @ssl_version = RHC::Helpers.parse_ssl_version(args[:ssl_version])
27
27
  @ssl_client_cert_file = args[:ssl_client_cert_file]
28
+ @ssl_client_key_file = args[:ssl_client_key_file]
28
29
  @ssl_ca_file = args[:ssl_ca_file]
29
30
  @default = args[:default]
30
31
  end
@@ -42,7 +43,7 @@ module RHC
42
43
  instance_variables.each do |k|
43
44
  h[k.to_s.delete('@')] = instance_variable_get(k)
44
45
  end
45
- end.reject{|k, v| v.nil? || k == 'default'}.inject({}){|h, (k, v)| h[k] = v.is_a?(String) ? v.to_s : v; h }
46
+ end.reject{|k, v| v.nil? || k == 'default'}.inject({}){|h, (k, v)| h[k] = v.is_a?(String) || v.is_a?(Symbol) ? v.to_s : v; h }
46
47
  end
47
48
 
48
49
  def to_config
@@ -152,6 +153,7 @@ module RHC
152
153
  :timeout => o[:timeout],
153
154
  :ssl_version => o[:ssl_version],
154
155
  :ssl_client_cert_file => o[:ssl_client_cert_file],
156
+ :ssl_client_key_file => o[:ssl_client_key_file],
155
157
  :ssl_ca_file => o[:ssl_ca_file])
156
158
  list.each{|server| server.default = server.hostname == o[:server]}
157
159
  end
@@ -189,4 +191,4 @@ module RHC
189
191
  s.present? && s.hostname != hostname ? nil : suggestion
190
192
  end
191
193
  end
192
- end
194
+ end
@@ -97,7 +97,13 @@ module RHC
97
97
  end
98
98
 
99
99
  def core_auth
100
- @core_auth ||= RHC::Auth::Basic.new(options)
100
+ @core_auth ||= begin
101
+ if options.ssl_client_cert_file && options.ssl_client_key_file
102
+ RHC::Auth::X509.new(options)
103
+ else
104
+ RHC::Auth::Basic.new(options)
105
+ end
106
+ end
101
107
  end
102
108
 
103
109
  def token_auth
@@ -144,6 +150,10 @@ module RHC
144
150
  ssh_keys.present? && ssh_keys.any? { |k| k.fingerprint.present? && k.fingerprint == fingerprint_for_default_key }
145
151
  end
146
152
 
153
+ def non_ssh_key_uploaded?
154
+ ssh_keys.present? && !ssh_keys.all?(&:is_ssh?)
155
+ end
156
+
147
157
  def existing_keys_info
148
158
  return unless ssh_keys
149
159
  indent{ ssh_keys.each{ |key| paragraph{ display_key(key) } } }
@@ -221,10 +231,12 @@ module RHC
221
231
  self.user = rest_client.user
222
232
  options.rhlogin = self.user.login unless username
223
233
 
224
- if rest_client.supports_sessions? && !options.token && options.create_token != false
234
+ if options.create_token == false
235
+ say "Skipping token generation..."
236
+ elsif rest_client.supports_sessions? && !options.token
225
237
  paragraph do
226
238
  info "OpenShift can create and store a token on disk which allows to you to access the server without using your password. The key is stored in your home directory and should be kept secret. You can delete the key at any time by running 'rhc logout'."
227
- if options.create_token or agree "Generate a token now? (yes|no) "
239
+ if agree "Generate a token now? (yes|no) "
228
240
  say "Generating an authorization token for this client ... "
229
241
  token = rest_client.new_session
230
242
  options.token = token.token
@@ -303,7 +315,7 @@ module RHC
303
315
  end
304
316
 
305
317
  def upload_ssh_key_stage
306
- return true if ssh_key_uploaded?
318
+ return true if ssh_key_uploaded? || non_ssh_key_uploaded?
307
319
 
308
320
  upload = paragraph do
309
321
  agree "Your public SSH key must be uploaded to the OpenShift server to access code. Upload now? (yes|no) "
@@ -530,7 +542,7 @@ module RHC
530
542
 
531
543
  # test connectivity an app
532
544
  def test_ssh_connectivity
533
- return true unless ssh_key_uploaded?
545
+ return true unless ssh_key_uploaded? || non_ssh_key_uploaded?
534
546
 
535
547
  applications.take(1).each do |app|
536
548
  begin
@@ -16,6 +16,8 @@ describe RHC::Auth::Basic do
16
16
  its(:options){ should_not be_nil }
17
17
  its(:can_authenticate?){ should be_false }
18
18
  its(:openshift_server){ should == 'openshift.redhat.com' }
19
+ its( :expired_token_message) { should == "Your authorization token has expired. Please sign in now to continue on #{subject.openshift_server}." }
20
+ its( :get_token_message) { should == "Please sign in to start a new session to #{subject.openshift_server}." }
19
21
 
20
22
  def resolved(hash)
21
23
  hash.each_pair do |k,v|
@@ -221,6 +223,68 @@ describe RHC::Auth::Basic do
221
223
  end
222
224
  end
223
225
 
226
+ describe RHC::Auth::X509 do
227
+ subject{ described_class.new(options) }
228
+
229
+ let(:default_options){ {} }
230
+ let(:options){ (o = Commander::Command::Options.new).default(default_options); o }
231
+ let(:a_cert){ OpenSSL::X509::Certificate.new }
232
+ let(:a_key){ OpenSSL::PKey::RSA.new }
233
+ its(:options){ should_not be_nil }
234
+ its(:can_authenticate?){ should be_true }
235
+ its(:openshift_server){ should == 'openshift.redhat.com' }
236
+ its(:expired_token_message) { should == "Your authorization token has expired. Fetching a new token from #{subject.openshift_server}."}
237
+ its(:get_token_message) { should == "Fetching a new token from #{subject.openshift_server}." }
238
+
239
+ describe "#retry_auth?" do
240
+ context "should return true if the response was 401" do
241
+ let(:response){ double(:status => 401) }
242
+ let(:client){ double }
243
+ it { subject.retry_auth?(response, client).should == true }
244
+ end
245
+
246
+ context "should return false if the response was 403" do
247
+ let(:response){ double }
248
+ let(:client){ double }
249
+ let(:response){ double(:status => 403) }
250
+ it { subject.retry_auth?(response, client).should == false }
251
+ end
252
+ end
253
+
254
+ describe "#to_request" do
255
+ let(:request){ {} }
256
+ let(:auth_hash){ {:client_cert => a_cert, :client_key => a_key} }
257
+
258
+ context "when a certificate exists" do
259
+ it "should use x509 auth" do
260
+ OpenSSL::X509::Certificate.should_receive(:new).exactly(1).times.and_return(a_cert)
261
+ OpenSSL::PKey::RSA.should_receive(:new).exactly(1).times.and_return(a_key)
262
+ subject.to_request(request).should == auth_hash
263
+ end
264
+ end
265
+
266
+ context "when a certificate can't be loaded" do
267
+ it "should send a debug message and raise an error" do
268
+ options.should_receive(:ssl_client_cert_file).and_return("a bogus path")
269
+ subject.should_receive(:debug)
270
+ expect do
271
+ subject.to_request(request)
272
+ end.to raise_error
273
+ end
274
+ end
275
+
276
+ context "when a key can't be loaded" do
277
+ it "should send a debug message and raise an error" do
278
+ options.should_receive(:ssl_client_key_file).and_return("a bogus path")
279
+ subject.should_receive(:debug)
280
+ expect do
281
+ subject.to_request(request)
282
+ end.to raise_error
283
+ end
284
+ end
285
+ end
286
+ end
287
+
224
288
  describe RHC::Auth::Token do
225
289
  subject{ described_class.new(options) }
226
290
 
@@ -317,7 +381,7 @@ describe RHC::Auth::Token do
317
381
  context "when token is not provided" do
318
382
  subject{ described_class.new(nil) }
319
383
 
320
- it("should pass not bearer token to the server"){ subject.to_request(request).should == {} }
384
+ it("should submit an empty bearer token to the server to trigger the 401 retry flow for OpenShift Enterprise"){ subject.to_request(request).should == {:headers => {'authorization' => "Bearer "}} }
321
385
  end
322
386
 
323
387
  context "when a parent auth class is passed" do
@@ -383,23 +447,28 @@ describe RHC::Auth::Token do
383
447
  before{ client.should_receive(:new_session).with(:auth => auth).and_return(auth_token) }
384
448
 
385
449
  it("should print a message") do
386
- subject.should_receive(:info).with("Please sign in to start a new session to #{subject.openshift_server}.")
450
+ subject.should_receive(:info).with("get token message")
387
451
  auth.should_receive(:retry_auth?).with(response, client).and_return true
452
+ auth.should_receive(:get_token_message).and_return("get token message")
388
453
  subject.retry_auth?(response, client).should be_true
389
454
  end
390
455
 
391
456
  context "with a token" do
392
457
  let(:default_options){ {:use_authorization_tokens => true, :token => 'foo'} }
393
458
  it("should invoke raise an error on retry because sessions are not supported") do
394
- subject.should_receive(:warn).with("Your authorization token has expired. Please sign in now to continue on #{subject.openshift_server}.")
459
+ subject.should_receive(:warn).with("expired token message")
395
460
  auth.should_receive(:retry_auth?).with(response, client).and_return true
461
+ auth.should_receive(:expired_token_message).and_return("expired token message")
396
462
  subject.retry_auth?(response, client).should be_true
397
463
  #expect{ subject.retry_auth?(response, client) }.to raise_error RHC::Rest::AuthorizationsNotSupported
398
464
  end
399
465
  end
400
466
 
401
467
  context "when the token request fails" do
402
- before{ subject.should_receive(:info).with("Please sign in to start a new session to #{subject.openshift_server}.") }
468
+ before do
469
+ subject.should_receive(:info).with("get token message")
470
+ auth.should_receive(:get_token_message).and_return("get token message")
471
+ end
403
472
  it("should invoke retry on the parent") do
404
473
  auth.should_receive(:retry_auth?).with(response, client).and_return false
405
474
  subject.retry_auth?(response, client).should be_false
@@ -408,7 +477,10 @@ describe RHC::Auth::Token do
408
477
 
409
478
  context "when the token request succeeds" do
410
479
  let(:auth_token){ double('auth_token', :token => 'bar') }
411
- before{ subject.should_receive(:info).with("Please sign in to start a new session to #{subject.openshift_server}.") }
480
+ before do
481
+ subject.should_receive(:info).with("get token message")
482
+ auth.should_receive(:get_token_message).and_return("get token message")
483
+ end
412
484
  it("should save the token and return true") do
413
485
  subject.should_receive(:save).with(auth_token.token).and_return true
414
486
  subject.retry_auth?(response, client).should be_true
@@ -325,12 +325,15 @@ describe RHC::Commands::Base do
325
325
 
326
326
  describe "rest_client" do
327
327
  let(:instance){ subject }
328
+ let(:options){ subject.send(:options) }
328
329
  before{ RHC::Rest::Client.any_instance.stub(:api_version_negotiated).and_return(1.4) }
329
330
 
330
331
  context "when initializing the object" do
331
332
  let(:auth){ double('auth') }
332
333
  let(:basic_auth){ double('basic_auth') }
333
- before{ RHC::Auth::Basic.should_receive(:new).at_least(1).times.with{ |arg| arg.should == instance.send(:options) }.and_return(basic_auth) }
334
+ let(:x509_auth){ double('x509_auth') }
335
+ before{ RHC::Auth::Basic.stub(:new).with{ |arg| arg.should == instance.send(:options) }.and_return(basic_auth) }
336
+ before{ RHC::Auth::X509.stub(:new).with{ |arg| arg.should == instance.send(:options) }.and_return(x509_auth) }
334
337
  before{ RHC::Auth::Token.stub(:new).with{ |arg, arg2, arg3| [arg, arg2, arg3].should == [instance.send(:options), basic_auth, instance.send(:token_store)] }.and_return(auth) }
335
338
 
336
339
  context "with no options" do
@@ -338,6 +341,15 @@ describe RHC::Commands::Base do
338
341
  it("should create only a basic auth object"){ subject.send(:rest_client) }
339
342
  end
340
343
 
344
+ context "with x509" do
345
+ before do
346
+ options.should_receive(:ssl_client_cert_file).and_return("a cert")
347
+ options.should_receive(:ssl_client_key_file).and_return("a key")
348
+ subject.should_receive(:client_from_options).with(:auth => x509_auth)
349
+ end
350
+ it("should create an x509 auth object"){ subject.send(:rest_client) }
351
+ end
352
+
341
353
  context "with use_authorization_tokens" do
342
354
  before{ subject.send(:options).use_authorization_tokens = true }
343
355
  before{ subject.should_receive(:client_from_options).with(:auth => auth) }
@@ -413,7 +425,7 @@ describe RHC::Commands::Base do
413
425
  let(:auth_token){ double(:token => 'a_token') }
414
426
  let(:arguments){ ['test', '-l', username, '--server', mock_uri] }
415
427
  before{ instance.send(:token_store).should_receive(:get).with{ |user, server| user.should == username; server.should == instance.send(:openshift_server) }.and_return(nil) }
416
- before{ stub_api(false, true); stub_api_request(:get, 'broker/rest/user', false).to_return{ |request| request.headers['Authorization'] =~ /Bearer/ ? simple_user(username) : {:status => 401} } }
428
+ before{ stub_api(false, true); stub_api_request(:get, 'broker/rest/user', false).to_return{ |request| request.headers['Authorization'] =~ /Bearer\s\w+/ ? simple_user(username) : {:status => 401} } }
417
429
  it("should attempt to create a new token") do
418
430
  rest_client.should_receive(:new_session).ordered.and_return(auth_token)
419
431
  rest_client.user
@@ -164,6 +164,7 @@ describe RHC::Commands::App do
164
164
  before{ RHC::Config.any_instance.stub(:has_local_config?).and_return(false) }
165
165
  before{ described_class.any_instance.stub(:interactive?).and_return(true) }
166
166
  before{ rest_client.domains.clear }
167
+ before{ rest_client.sshkeys.delete_if {|k| !k.is_ssh? } }
167
168
  let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1'] }
168
169
  # skips login stage and insecure check because of mock rest client, doesn't check keys
169
170
  it { run_output(['mydomain', 'y', 'mykey']).should match(/This wizard.*Checking for a domain.*You will not be able to create an application without completing this step.*Your domain 'mydomain' has been successfully created.*Creating application.*Your public SSH key.*Uploading key 'mykey'.*Your application 'app1' is now available.*Cloned to/m) }
@@ -730,16 +731,28 @@ describe RHC::Commands::App do
730
731
  end
731
732
 
732
733
  describe 'app show --gears' do
733
- let(:arguments) { ['app', 'show', 'app1', '--gears'] }
734
+ let(:arguments) { ['app', 'show', 'app1', '--gears', '--raw'] }
734
735
 
735
736
  context 'when run' do
736
737
  before do
737
738
  @domain = rest_client.add_domain("mockdomain")
738
739
  @domain.add_application("app1", "mock_type")
739
740
  end
741
+ it { run_output.should match(/ID\s+State\s+Cartridges\s+Size\s+SSH URL/) }
740
742
  it { run_output.should match("fakegearid0 started mock_type small fakegearid0@fakesshurl.com") }
741
743
  it { expect{ run }.to exit_with_code(0) }
742
744
  end
745
+
746
+ context 'with regions and zones' do
747
+ before do
748
+ @domain = rest_client.add_domain("mockdomain")
749
+ @app = @domain.add_application("app1", "mock_type")
750
+ @app.gears.each{|g| g['region'] = 'south'; g['zone'] = 'west'}
751
+ end
752
+ it { run_output.should match(/ID\s+State\s+Cartridges\s+Size\s+Region\s+Zone\s+SSH URL/) }
753
+ it { run_output.should match(/fakegearid0\s+started\s+mock_type\s+small\s+south\s+west\s+fakegearid0@fakesshurl.com/) }
754
+ it { expect{ run }.to exit_with_code(0) }
755
+ end
743
756
  end
744
757
 
745
758
  describe 'app show --gears quota' do
@@ -246,7 +246,7 @@ describe RHC::Commands::Env do
246
246
  let(:arguments) { args }
247
247
  it { succeed_with_message /TEST_ENV_VAR/ }
248
248
  it { succeed_with_message /Removing environment variable\(s\) \.\.\./ }
249
- it { succeed_with_message /removed/ }
249
+ it { succeed_with_message /done/ }
250
250
  end
251
251
  end
252
252
 
@@ -259,7 +259,7 @@ describe RHC::Commands::Env do
259
259
  it { succeed_with_message /TEST_ENV_VAR2/ }
260
260
  it { succeed_with_message /TEST_ENV_VAR3/ }
261
261
  it { succeed_with_message /Removing environment variable\(s\) \.\.\./ }
262
- it { succeed_with_message /removed/ }
262
+ it { succeed_with_message /done/ }
263
263
  end
264
264
  end
265
265
 
@@ -71,6 +71,13 @@ describe RHC::Commands::Member do
71
71
  it("should include the login value") { run_output.should =~ /alice.*Bob.*carol.*doug@doug\.com/m }
72
72
  end
73
73
 
74
+ context 'without membership support' do
75
+ let(:arguments) { ['domain', 'show', 'mock-domain-0'] }
76
+ before{ with_mock_domain }
77
+ let(:supports_members){ false }
78
+ it { expect { run }.to exit_with_code(0) }
79
+ it { run_output.should_not =~ /owned by/ }
80
+ end
74
81
  end
75
82
 
76
83
  describe 'list-member' do
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'rest_spec_helper'
3
+ require 'rhc/commands/region'
4
+ require 'rhc/config'
5
+
6
+ describe RHC::Commands::Region do
7
+ before{ user_config }
8
+
9
+ describe 'region list' do
10
+ let(:arguments){ ['region', 'list'] }
11
+ let(:username){ nil }
12
+ let(:password){ nil }
13
+ let(:server){ mock_uri }
14
+ let(:user_auth){ false }
15
+
16
+ context 'with server regions' do
17
+ before do
18
+ stub_api
19
+ stub_simple_regions
20
+ end
21
+
22
+ it{ run_output.should match /Server test\.domain\.com$/ }
23
+ it{ run_output.should match /Region 'north' \(uuid: region0001\)$/ }
24
+ it{ run_output.should match /Description:\s+Servers in the north of US$/ }
25
+ it{ run_output.should match /Available Zones:\s+east, west$/ }
26
+ it{ run_output.should match /Region 'south' \(uuid: region0002\) \(default\)/ }
27
+ it{ run_output.should match /Available Zones: east$/ }
28
+ it{ expect{ run }.to exit_with_code(0) }
29
+ end
30
+
31
+ context 'without server regions' do
32
+ before do
33
+ stub_api
34
+ stub_simple_regions(true)
35
+ end
36
+
37
+ it{ run_output.should_not match /Available Zones$/ }
38
+ it{ run_output.should match /Server doesn't have any regions or zones configured/ }
39
+ it{ expect{ run }.to exit_with_code(169) }
40
+ end
41
+
42
+ context 'regions not supported on server' do
43
+ let!(:rest_client){ MockRestClient.new }
44
+ it{ run_output.should_not match /Available Zones$/ }
45
+ it{ run_output.should match /Server does not support regions and zones/ }
46
+ it{ expect{ run }.to exit_with_code(168) }
47
+ end
48
+ end
49
+
50
+ end