startapp 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYRIGHT +1 -0
- data/LICENSE +11 -0
- data/README.md +95 -0
- data/Rakefile +6 -0
- data/autocomplete/rhc_bash +1672 -0
- data/bin/app +37 -0
- data/conf/express.conf +8 -0
- data/features/assets/deploy.tar.gz +0 -0
- data/features/core_feature.rb +191 -0
- data/features/deployments_feature.rb +129 -0
- data/features/domains_feature.rb +58 -0
- data/features/keys_feature.rb +37 -0
- data/features/members_feature.rb +166 -0
- data/lib/rhc/auth/basic.rb +64 -0
- data/lib/rhc/auth/token.rb +102 -0
- data/lib/rhc/auth/token_store.rb +53 -0
- data/lib/rhc/auth.rb +5 -0
- data/lib/rhc/autocomplete.rb +66 -0
- data/lib/rhc/autocomplete_templates/bash.erb +39 -0
- data/lib/rhc/cartridge_helpers.rb +118 -0
- data/lib/rhc/cli.rb +40 -0
- data/lib/rhc/command_runner.rb +185 -0
- data/lib/rhc/commands/account.rb +25 -0
- data/lib/rhc/commands/alias.rb +124 -0
- data/lib/rhc/commands/app.rb +726 -0
- data/lib/rhc/commands/apps.rb +20 -0
- data/lib/rhc/commands/authorization.rb +115 -0
- data/lib/rhc/commands/base.rb +174 -0
- data/lib/rhc/commands/cartridge.rb +329 -0
- data/lib/rhc/commands/clone.rb +66 -0
- data/lib/rhc/commands/configure.rb +20 -0
- data/lib/rhc/commands/create.rb +100 -0
- data/lib/rhc/commands/delete.rb +19 -0
- data/lib/rhc/commands/deploy.rb +32 -0
- data/lib/rhc/commands/deployment.rb +82 -0
- data/lib/rhc/commands/domain.rb +172 -0
- data/lib/rhc/commands/env.rb +142 -0
- data/lib/rhc/commands/force_stop.rb +17 -0
- data/lib/rhc/commands/git_clone.rb +34 -0
- data/lib/rhc/commands/logout.rb +51 -0
- data/lib/rhc/commands/logs.rb +21 -0
- data/lib/rhc/commands/member.rb +148 -0
- data/lib/rhc/commands/port_forward.rb +197 -0
- data/lib/rhc/commands/reload.rb +17 -0
- data/lib/rhc/commands/restart.rb +17 -0
- data/lib/rhc/commands/scp.rb +54 -0
- data/lib/rhc/commands/server.rb +40 -0
- data/lib/rhc/commands/setup.rb +60 -0
- data/lib/rhc/commands/show.rb +43 -0
- data/lib/rhc/commands/snapshot.rb +137 -0
- data/lib/rhc/commands/ssh.rb +51 -0
- data/lib/rhc/commands/sshkey.rb +97 -0
- data/lib/rhc/commands/start.rb +17 -0
- data/lib/rhc/commands/stop.rb +17 -0
- data/lib/rhc/commands/tail.rb +47 -0
- data/lib/rhc/commands/threaddump.rb +14 -0
- data/lib/rhc/commands/tidy.rb +17 -0
- data/lib/rhc/commands.rb +396 -0
- data/lib/rhc/config.rb +321 -0
- data/lib/rhc/context_helper.rb +121 -0
- data/lib/rhc/core_ext.rb +202 -0
- data/lib/rhc/coverage_helper.rb +33 -0
- data/lib/rhc/deployment_helpers.rb +111 -0
- data/lib/rhc/exceptions.rb +256 -0
- data/lib/rhc/git_helpers.rb +106 -0
- data/lib/rhc/help_formatter.rb +55 -0
- data/lib/rhc/helpers.rb +481 -0
- data/lib/rhc/highline_extensions.rb +479 -0
- data/lib/rhc/json.rb +51 -0
- data/lib/rhc/output_helpers.rb +260 -0
- data/lib/rhc/rest/activation.rb +11 -0
- data/lib/rhc/rest/alias.rb +42 -0
- data/lib/rhc/rest/api.rb +87 -0
- data/lib/rhc/rest/application.rb +348 -0
- data/lib/rhc/rest/attributes.rb +36 -0
- data/lib/rhc/rest/authorization.rb +8 -0
- data/lib/rhc/rest/base.rb +79 -0
- data/lib/rhc/rest/cartridge.rb +162 -0
- data/lib/rhc/rest/client.rb +650 -0
- data/lib/rhc/rest/deployment.rb +18 -0
- data/lib/rhc/rest/domain.rb +98 -0
- data/lib/rhc/rest/environment_variable.rb +15 -0
- data/lib/rhc/rest/gear_group.rb +16 -0
- data/lib/rhc/rest/httpclient.rb +145 -0
- data/lib/rhc/rest/key.rb +44 -0
- data/lib/rhc/rest/membership.rb +105 -0
- data/lib/rhc/rest/mock.rb +1042 -0
- data/lib/rhc/rest/user.rb +32 -0
- data/lib/rhc/rest.rb +148 -0
- data/lib/rhc/scp_helpers.rb +27 -0
- data/lib/rhc/ssh_helpers.rb +380 -0
- data/lib/rhc/tar_gz.rb +51 -0
- data/lib/rhc/usage_templates/command_help.erb +51 -0
- data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
- data/lib/rhc/usage_templates/help.erb +61 -0
- data/lib/rhc/usage_templates/missing_help.erb +1 -0
- data/lib/rhc/usage_templates/options_help.erb +12 -0
- data/lib/rhc/vendor/okjson.rb +600 -0
- data/lib/rhc/vendor/parseconfig.rb +178 -0
- data/lib/rhc/vendor/sshkey.rb +253 -0
- data/lib/rhc/vendor/zliby.rb +628 -0
- data/lib/rhc/version.rb +5 -0
- data/lib/rhc/wizard.rb +637 -0
- data/lib/rhc.rb +34 -0
- data/spec/coverage_helper.rb +82 -0
- data/spec/direct_execution_helper.rb +339 -0
- data/spec/keys/example.pem +23 -0
- data/spec/keys/example_private.pem +27 -0
- data/spec/keys/server.pem +19 -0
- data/spec/rest_spec_helper.rb +31 -0
- data/spec/rhc/assets/cert.crt +22 -0
- data/spec/rhc/assets/cert_key_rsa +27 -0
- data/spec/rhc/assets/empty.txt +0 -0
- data/spec/rhc/assets/env_vars.txt +7 -0
- data/spec/rhc/assets/env_vars_2.txt +1 -0
- data/spec/rhc/assets/foo.txt +1 -0
- data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
- data/spec/rhc/assets/targz_sample.tar.gz +0 -0
- data/spec/rhc/auth_spec.rb +442 -0
- data/spec/rhc/cli_spec.rb +186 -0
- data/spec/rhc/command_spec.rb +435 -0
- data/spec/rhc/commands/account_spec.rb +42 -0
- data/spec/rhc/commands/alias_spec.rb +333 -0
- data/spec/rhc/commands/app_spec.rb +777 -0
- data/spec/rhc/commands/apps_spec.rb +39 -0
- data/spec/rhc/commands/authorization_spec.rb +157 -0
- data/spec/rhc/commands/cartridge_spec.rb +665 -0
- data/spec/rhc/commands/clone_spec.rb +41 -0
- data/spec/rhc/commands/deployment_spec.rb +327 -0
- data/spec/rhc/commands/domain_spec.rb +401 -0
- data/spec/rhc/commands/env_spec.rb +493 -0
- data/spec/rhc/commands/git_clone_spec.rb +102 -0
- data/spec/rhc/commands/logout_spec.rb +86 -0
- data/spec/rhc/commands/member_spec.rb +247 -0
- data/spec/rhc/commands/port_forward_spec.rb +217 -0
- data/spec/rhc/commands/scp_spec.rb +77 -0
- data/spec/rhc/commands/server_spec.rb +69 -0
- data/spec/rhc/commands/setup_spec.rb +118 -0
- data/spec/rhc/commands/snapshot_spec.rb +179 -0
- data/spec/rhc/commands/ssh_spec.rb +163 -0
- data/spec/rhc/commands/sshkey_spec.rb +188 -0
- data/spec/rhc/commands/tail_spec.rb +81 -0
- data/spec/rhc/commands/threaddump_spec.rb +84 -0
- data/spec/rhc/config_spec.rb +407 -0
- data/spec/rhc/helpers_spec.rb +531 -0
- data/spec/rhc/highline_extensions_spec.rb +314 -0
- data/spec/rhc/json_spec.rb +30 -0
- data/spec/rhc/rest_application_spec.rb +258 -0
- data/spec/rhc/rest_client_spec.rb +752 -0
- data/spec/rhc/rest_spec.rb +740 -0
- data/spec/rhc/targz_spec.rb +55 -0
- data/spec/rhc/wizard_spec.rb +756 -0
- data/spec/spec_helper.rb +575 -0
- data/spec/wizard_spec_helper.rb +330 -0
- metadata +469 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rest_spec_helper'
|
3
|
+
require 'rhc'
|
4
|
+
require 'rhc/commands/logout'
|
5
|
+
|
6
|
+
describe RHC::Commands::Logout do
|
7
|
+
|
8
|
+
describe '#run' do
|
9
|
+
let(:arguments) { ['logout'] }
|
10
|
+
let(:username) { 'foo' }
|
11
|
+
let(:password) { nil }
|
12
|
+
let(:supports_auth) { false }
|
13
|
+
let(:server) { mock_uri }
|
14
|
+
let!(:token_store) { RHC::Auth::TokenStore.new(Dir.mktmpdir) }
|
15
|
+
before{ user_config }
|
16
|
+
before do
|
17
|
+
stub_api(false, supports_auth)
|
18
|
+
challenge{ stub_user }
|
19
|
+
RHC::Auth::TokenStore.stub(:new).and_return(token_store)
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when calling from the alias" do
|
23
|
+
let(:arguments){ ['account', 'logout', '-h'] }
|
24
|
+
it("should print usage"){ run_output.should match "Usage: app logout" }
|
25
|
+
end
|
26
|
+
|
27
|
+
it("should clear the token cache"){ expect{ run }.to call(:clear).on(token_store) }
|
28
|
+
it("should exit with success"){ expect{ run }.to exit_with_code(0) }
|
29
|
+
it("should display a message"){ run_output.should match("All local sessions removed.") }
|
30
|
+
|
31
|
+
context "when --all is requested" do
|
32
|
+
let(:arguments) { ['account', 'logout', '--all'] }
|
33
|
+
|
34
|
+
context "if the server does not implement authorizations" do
|
35
|
+
it("should display a message"){ run_output.should match(/Deleting all authorizations associated with your account.*not supported/) }
|
36
|
+
it("should exit with success"){ expect{ run }.to exit_with_code(0) }
|
37
|
+
end
|
38
|
+
|
39
|
+
context "if the server implements authorizations" do
|
40
|
+
let(:supports_auth) { true }
|
41
|
+
before{ stub_delete_authorizations }
|
42
|
+
|
43
|
+
it("should display a message"){ run_output.should match(/Deleting all authorizations associated with your account.*done/) }
|
44
|
+
it("should exit with success"){ expect{ run }.to exit_with_code(0) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when --token is provided" do
|
49
|
+
let(:arguments) { ['account', 'logout', '--token', 'foo'] }
|
50
|
+
def user_auth; { :token => 'foo' }; end
|
51
|
+
|
52
|
+
context "if the server does not implement authorizations" do
|
53
|
+
it("should display a message"){ run_output.should match(/Ending session on server.*not supported/) }
|
54
|
+
it("should exit with success"){ expect{ run }.to exit_with_code(0) }
|
55
|
+
end
|
56
|
+
|
57
|
+
context "if the server implements authorizations" do
|
58
|
+
let(:supports_auth) { true }
|
59
|
+
|
60
|
+
context "if the server returns successfully" do
|
61
|
+
before{ stub_delete_authorization('foo') }
|
62
|
+
|
63
|
+
it("should display a message"){ run_output.should match(/Ending session on server.*deleted/) }
|
64
|
+
it("should exit with success"){ expect{ run }.to exit_with_code(0) }
|
65
|
+
it("should clear the token cache"){ expect{ run }.to call(:clear).on(token_store) }
|
66
|
+
end
|
67
|
+
|
68
|
+
context "if the server rejects the token" do
|
69
|
+
before{ stub_request(:delete, mock_href('broker/rest/user/authorizations/foo', false)).to_return(:status => 401, :body => {}.to_json) }
|
70
|
+
|
71
|
+
it("should display a message"){ run_output.should match(/Ending session on server.*already closed/) }
|
72
|
+
it("should exit with success"){ expect{ run }.to exit_with_code(0) }
|
73
|
+
it("should clear the token cache"){ expect{ run }.to call(:clear).on(token_store) }
|
74
|
+
end
|
75
|
+
|
76
|
+
context "if the server returns an unexpected error" do
|
77
|
+
before{ stub_request(:delete, mock_href('broker/rest/user/authorizations/foo', false)).to_return(:status => 500, :body => {}.to_json) }
|
78
|
+
|
79
|
+
it("should display a message"){ run_output.should match(/Ending session on server.*The server did not respond/) }
|
80
|
+
it("should exit with success"){ expect{ run }.to exit_with_code(0) }
|
81
|
+
it("should clear the token cache"){ expect{ run }.to call(:clear).on(token_store) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rest_spec_helper'
|
3
|
+
require 'rhc/commands/member'
|
4
|
+
|
5
|
+
describe RHC::Commands::Member do
|
6
|
+
|
7
|
+
before{ user_config }
|
8
|
+
|
9
|
+
describe 'help' do
|
10
|
+
let(:arguments) { ['member', '--help'] }
|
11
|
+
|
12
|
+
it "should display help" do
|
13
|
+
expect { run }.to exit_with_code(0)
|
14
|
+
end
|
15
|
+
it('should output usage') { run_output.should match "Usage: app member" }
|
16
|
+
it('should output info about roles') { run_output.should match "Teams of developers can collaborate" }
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:username){ 'test_user' }
|
20
|
+
let(:password){ 'test_password' }
|
21
|
+
let(:server){ 'test.domain.com' }
|
22
|
+
|
23
|
+
def with_mock_rest_client
|
24
|
+
@rest_client ||= MockRestClient.new
|
25
|
+
end
|
26
|
+
def with_mock_domain
|
27
|
+
@domain ||= with_mock_rest_client.add_domain("mock-domain-0")
|
28
|
+
end
|
29
|
+
def with_mock_app
|
30
|
+
@app ||= begin
|
31
|
+
app = with_mock_domain.add_application("mock-app-0", "ruby-1.8.7")
|
32
|
+
app.stub(:ssh_url).and_return("ssh://user@test.domain.com")
|
33
|
+
app.stub(:supports_members?).and_return(supports_members)
|
34
|
+
app
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:owner){ RHC::Rest::Membership::Member.new(:id => '1', :role => 'admin', :owner => true, :login => 'alice') }
|
39
|
+
let(:other_admin){ RHC::Rest::Membership::Member.new(:id => '2', :role => 'admin', :login => 'Bob') }
|
40
|
+
let(:other_editor){ RHC::Rest::Membership::Member.new(:id => '3', :role => 'edit', :name => 'Carol', :login => 'carol') }
|
41
|
+
let(:other_viewer){ RHC::Rest::Membership::Member.new(:id => '4', :role => 'view', :name => 'Doug', :login => 'doug@doug.com') }
|
42
|
+
let(:other_viewer2){ RHC::Rest::Membership::Member.new(:id => '5', :role => 'view', :name => 'ViewerC', :login => 'viewerc@viewer.com') }
|
43
|
+
let(:other_viewer3){ RHC::Rest::Membership::Member.new(:id => '6', :role => 'view', :name => 'ViewerB', :login => 'viewerb@viewer.com') }
|
44
|
+
let(:other_viewer4){ RHC::Rest::Membership::Member.new(:id => '7', :role => 'view', :name => 'ViewerA', :login => 'viewera@viewer.com') }
|
45
|
+
|
46
|
+
describe 'show-domain' do
|
47
|
+
context 'with members' do
|
48
|
+
let(:arguments) { ['domain', 'show', 'mock-domain-0'] }
|
49
|
+
let(:supports_members){ true }
|
50
|
+
|
51
|
+
before{ with_mock_domain.add_member(owner).add_member(other_editor).add_member(other_admin).add_member(other_viewer).add_member(other_viewer2).add_member(other_viewer3).add_member(other_viewer4) }
|
52
|
+
it { expect { run }.to exit_with_code(0) }
|
53
|
+
it { run_output.should =~ /owned by alice/ }
|
54
|
+
it { run_output.should =~ /Bob\s+\(admin\)/ }
|
55
|
+
it { run_output.should =~ /<carol>\s+\(edit\)/ }
|
56
|
+
it { run_output.should =~ /<doug@doug\.com>\s+\(view\)/ }
|
57
|
+
it("should order the members by role then by name") { run_output.should =~ /Bob.*admin.*Admins.*Carol.*Editors.*ViewerA.*ViewerB.*ViewerC.*Viewers/m }
|
58
|
+
it("should include the login value") { run_output.should =~ /alice.*Bob.*carol.*doug@doug\.com/m }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'list-member' do
|
63
|
+
context 'on a domain' do
|
64
|
+
let(:arguments) { ['members', '-n', 'mock-domain-0'] }
|
65
|
+
let(:supports_members){ true }
|
66
|
+
before{ with_mock_domain.add_member(owner) }
|
67
|
+
it { expect { run }.to exit_with_code(0) }
|
68
|
+
it { run_output.should =~ /alice\s+admin \(owner\)/ }
|
69
|
+
it("should not show the name column") { run_output.should =~ /^Login\s+Role$/ }
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'on an application' do
|
73
|
+
let(:arguments) { ['members', 'mock-domain-0/mock-app-0'] }
|
74
|
+
let(:supports_members){ false }
|
75
|
+
before{ with_mock_app }
|
76
|
+
|
77
|
+
it { expect { run }.to exit_with_code(1) }
|
78
|
+
it { run_output.should =~ /The server does not support adding or removing members/ }
|
79
|
+
|
80
|
+
context "with only owner" do
|
81
|
+
let(:supports_members){ true }
|
82
|
+
before{ with_mock_app.add_member(owner) }
|
83
|
+
it { expect { run }.to exit_with_code(0) }
|
84
|
+
it { run_output.should =~ /alice\s+admin \(owner\)/ }
|
85
|
+
it("should not show the name column") { run_output.should =~ /^Login\s+Role$/ }
|
86
|
+
|
87
|
+
context "with ids" do
|
88
|
+
let(:arguments) { ['members', 'mock-domain-0/mock-app-0', '--ids'] }
|
89
|
+
it { expect { run }.to exit_with_code(0) }
|
90
|
+
it { run_output.should =~ /alice\s+admin \(owner\) 1/ }
|
91
|
+
it("should not show the name column") { run_output.should =~ /^Login\s+Role\s+ID$/ }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "with several members" do
|
96
|
+
let(:supports_members){ true }
|
97
|
+
before{ with_mock_app.add_member(owner).add_member(other_editor).add_member(other_admin).add_member(other_viewer) }
|
98
|
+
it { expect { run }.to exit_with_code(0) }
|
99
|
+
it { run_output.should =~ /alice\s+admin \(owner\)/ }
|
100
|
+
it { run_output.should =~ /Bob\s+admin/ }
|
101
|
+
it { run_output.should =~ /carol\s+edit/ }
|
102
|
+
it { run_output.should =~ /doug\.com\s+view/ }
|
103
|
+
it("should order the members by role") { run_output.should =~ /admin.*owner.*admin.*edit.*view/m }
|
104
|
+
it("should include the login value") { run_output.should =~ /alice.*Bob.*carol.*doug@doug\.com/m }
|
105
|
+
it("should show the name column") { run_output.should =~ /^Name\s+Login\s+Role$/ }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'add-member' do
|
111
|
+
before do
|
112
|
+
stub_api
|
113
|
+
challenge{ stub_one_domain('test', nil, mock_user_auth) }
|
114
|
+
end
|
115
|
+
|
116
|
+
context "when the resource doesn't support membership changes" do
|
117
|
+
let(:arguments) { ['add-member', 'testuser', '-n', 'test'] }
|
118
|
+
it { expect { run }.to exit_with_code(1) }
|
119
|
+
it { run_output.should =~ /Adding 1 editor to domain .*The server does not support adding or removing members/ }
|
120
|
+
end
|
121
|
+
|
122
|
+
context "with supported membership" do
|
123
|
+
let(:supports_members?){ true }
|
124
|
+
|
125
|
+
context 'with a valid user' do
|
126
|
+
let(:arguments) { ['add-member', 'testuser', '-n', 'test'] }
|
127
|
+
before do
|
128
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
129
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'edit'}]}).
|
130
|
+
to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Added 1 member'},]}.to_json, :status => 200})
|
131
|
+
end
|
132
|
+
it { expect { run }.to exit_with_code(0) }
|
133
|
+
it { run_output.should =~ /Adding 1 editor to domain .*done/ }
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'with an invalid role' do
|
137
|
+
let(:arguments) { ['add-member', 'testuser', '-n', 'test', '--role', 'missing'] }
|
138
|
+
it { expect { run }.to exit_with_code(1) }
|
139
|
+
it { run_output.should =~ /The provided role 'missing' is not valid\. Supported values: .*admin/ }
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'with a missing user' do
|
143
|
+
let(:arguments) { ['add-member', 'testuser', '-n', 'test'] }
|
144
|
+
before do
|
145
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
146
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'edit'}]}).
|
147
|
+
to_return({:body => {:messages => [{:exit_code => 132, :field => 'login', :index => 0, :severity => 'error', :text => 'There is no user with a login testuser'},]}.to_json, :status => 422})
|
148
|
+
end
|
149
|
+
it { expect { run }.to exit_with_code(1) }
|
150
|
+
it { run_output.should =~ /Adding 1 editor to domain.*There is no user with a login testuser/ }
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'with a missing user id and role' do
|
154
|
+
let(:arguments) { ['add-member', '123', '-n', 'test', '--ids', '--role', 'admin'] }
|
155
|
+
before do
|
156
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
157
|
+
with(:body => {:members => [{'id' => '123', 'role' => 'admin'}]}).
|
158
|
+
to_return({:body => {:messages => [{:exit_code => 132, :field => 'id', :index => 0, :severity => 'error', :text => 'There is no user with the id 123'},]}.to_json, :status => 422})
|
159
|
+
end
|
160
|
+
it { expect { run }.to exit_with_code(1) }
|
161
|
+
it { run_output.should =~ /Adding 1 administrator to domain.*There is no user with the id 123/ }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe 'remove-member' do
|
167
|
+
context "when the resource doesn't support membership changes" do
|
168
|
+
before{ stub_api }
|
169
|
+
|
170
|
+
context "when adjusting a domain" do
|
171
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] }
|
172
|
+
before{ challenge{ stub_one_domain('test', nil, mock_user_auth) } }
|
173
|
+
it { expect { run }.to exit_with_code(1) }
|
174
|
+
it { run_output.should =~ /Removing 1 member from domain .*The server does not support adding or removing members/ }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "with supported membership" do
|
179
|
+
let(:supports_members?){ true }
|
180
|
+
before do
|
181
|
+
stub_api
|
182
|
+
challenge{ stub_one_domain('test', nil, mock_user_auth) }
|
183
|
+
end
|
184
|
+
=begin Scenario removed
|
185
|
+
context "when adjusting an app" do
|
186
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test', '-a', 'app'] }
|
187
|
+
before{ challenge{ stub_one_application('test', 'app') } }
|
188
|
+
it { expect { run }.to exit_with_code(1) }
|
189
|
+
it { run_output.should =~ /You can only add or remove members on a domain/ }
|
190
|
+
end
|
191
|
+
=end
|
192
|
+
context 'with a valid member' do
|
193
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] }
|
194
|
+
before do
|
195
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
196
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'none'}]}).
|
197
|
+
to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Removed 1 member'},]}.to_json, :status => 200})
|
198
|
+
end
|
199
|
+
it { expect { run }.to exit_with_code(0) }
|
200
|
+
it { run_output.should =~ /Removing 1 member from domain .*done/ }
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'with --all' do
|
204
|
+
let(:arguments) { ['remove-member', '--all', '-n', 'test'] }
|
205
|
+
before do
|
206
|
+
stub_api_request(:delete, "broker/rest/domains/test/members").
|
207
|
+
to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Removed everyone except owner.'},]}.to_json, :status => 200})
|
208
|
+
end
|
209
|
+
it { expect { run }.to exit_with_code(0) }
|
210
|
+
it { run_output.should =~ /Removing all members from domain .*done/ }
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'with a missing user' do
|
214
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] }
|
215
|
+
before do
|
216
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
217
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'none'}]}).
|
218
|
+
to_return({:body => {:messages => [{:exit_code => 132, :field => 'login', :index => 0, :severity => 'error', :text => 'There is no user with a login testuser'},]}.to_json, :status => 422})
|
219
|
+
end
|
220
|
+
it { expect { run }.to exit_with_code(1) }
|
221
|
+
it { run_output.should =~ /Removing 1 member from domain.*There is no user with a login testuser/ }
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'with a missing user id and role' do
|
225
|
+
let(:arguments) { ['remove-member', '123', '-n', 'test', '--ids'] }
|
226
|
+
before do
|
227
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
228
|
+
with(:body => {:members => [{'id' => '123', 'role' => 'none'}]}).
|
229
|
+
to_return({:body => {:messages => [{:exit_code => 132, :field => 'id', :index => 0, :severity => 'error', :text => 'There is no user with the id 123'},]}.to_json, :status => 422})
|
230
|
+
end
|
231
|
+
it { expect { run }.to exit_with_code(1) }
|
232
|
+
it { run_output.should =~ /Removing 1 member from domain.*There is no user with the id 123/ }
|
233
|
+
end
|
234
|
+
|
235
|
+
context 'when the user isn''t a member' do
|
236
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] }
|
237
|
+
before do
|
238
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
239
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'none'}]}).
|
240
|
+
to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'warning', :text => 'testuser is not a member of this domain.'},]}.to_json, :status => 200})
|
241
|
+
end
|
242
|
+
it { expect { run }.to exit_with_code(0) }
|
243
|
+
it { run_output.should =~ /Removing 1 member from domain.*testuser is not a member of this domain.*done/m }
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rest_spec_helper'
|
3
|
+
require 'rhc/commands/port_forward'
|
4
|
+
|
5
|
+
describe RHC::Commands::PortForward do
|
6
|
+
|
7
|
+
let!(:rest_client){ MockRestClient.new }
|
8
|
+
before{ user_config }
|
9
|
+
|
10
|
+
describe 'run' do
|
11
|
+
let(:arguments) { ['port-forward', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp'] }
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
@domain = rest_client.add_domain("mockdomain")
|
15
|
+
@app = @domain.add_application 'mockapp', 'mock-1.0'
|
16
|
+
@uri = URI.parse @app.ssh_url
|
17
|
+
@ssh = double(Net::SSH)
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when port forwarding for a down appl' do
|
21
|
+
before(:each) do
|
22
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh)
|
23
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, '127.0.0.1:3306')
|
24
|
+
@gg = MockRestGearGroup.new(rest_client)
|
25
|
+
@app.should_receive(:gear_groups).and_return([@gg])
|
26
|
+
@gg.should_receive(:gears).and_return([{'state' => 'stopped', 'id' => 'fakegearid'}])
|
27
|
+
end
|
28
|
+
it "should error out and suggest restarting the application" do
|
29
|
+
expect { run }.to exit_with_code(1)
|
30
|
+
end
|
31
|
+
it { run_output.should match(/none.*The application is stopped\..*restart/m) }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when port forwarding an app without ports to forward' do
|
35
|
+
before(:each) do
|
36
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh)
|
37
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, '127.0.0.1:3306')
|
38
|
+
end
|
39
|
+
it "should error out as no ports to forward" do
|
40
|
+
expect { run }.to exit_with_code(102)
|
41
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
42
|
+
rest_client.domains[0].applications.size.should == 1
|
43
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
44
|
+
end
|
45
|
+
it("should report no ports") { run_output.should match("no available ports to forward.") }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when port forwarding an app with permission denied ports' do
|
49
|
+
before(:each) do
|
50
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh)
|
51
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'permission denied')
|
52
|
+
end
|
53
|
+
it "should error out as permission denied" do
|
54
|
+
expect { run }.to exit_with_code(129)
|
55
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
56
|
+
rest_client.domains[0].applications.size.should == 1
|
57
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
58
|
+
end
|
59
|
+
it { run_output.should match("Permission denied") }
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when port forwarding an app with ports to forward' do
|
63
|
+
before(:each) do
|
64
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
65
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306')
|
66
|
+
forward = double(Net::SSH::Service::Forward)
|
67
|
+
@ssh.should_receive(:forward).and_return(forward)
|
68
|
+
forward.should_receive(:local).with(3306, '127.0.0.1', 3306)
|
69
|
+
@ssh.should_receive(:loop)
|
70
|
+
end
|
71
|
+
it "should run successfully" do
|
72
|
+
expect { run }.to exit_with_code(0)
|
73
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
74
|
+
rest_client.domains[0].applications.size.should == 1
|
75
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
76
|
+
end
|
77
|
+
it { run_output.should match(/Forwarding ports.*Press CTRL-C/m) }
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when host is unreachable' do
|
81
|
+
before(:each) do
|
82
|
+
Net::SSH.should_receive(:start).and_raise(Errno::EHOSTUNREACH)
|
83
|
+
end
|
84
|
+
it "should error out" do
|
85
|
+
expect { run }.to exit_with_code(1)
|
86
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
87
|
+
rest_client.domains[0].applications.size.should == 1
|
88
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
89
|
+
end
|
90
|
+
it { run_output.should include("Error trying to forward ports.") }
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when REST client connection times out' do
|
94
|
+
before(:each) do
|
95
|
+
rest_client.should_receive(:find_domain).and_raise(RHC::Rest::ConnectionException)
|
96
|
+
end
|
97
|
+
it("should error out") { expect { run }.to exit_with_code(1) }
|
98
|
+
it{ run_output.should match("Connection.*failed:") }
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when port forwarding an app with ports to forward' do
|
102
|
+
before(:each) do
|
103
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
104
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306')
|
105
|
+
forward = double(Net::SSH::Service::Forward)
|
106
|
+
@ssh.should_receive(:forward).and_return(forward)
|
107
|
+
forward.should_receive(:local).with(3306, '127.0.0.1', 3306)
|
108
|
+
@ssh.should_receive(:loop).and_raise(Interrupt.new)
|
109
|
+
end
|
110
|
+
it "should exit when user interrupts" do
|
111
|
+
expect { run }.to exit_with_code(0)
|
112
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
113
|
+
rest_client.domains[0].applications.size.should == 1
|
114
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
115
|
+
end
|
116
|
+
it { run_output.should include("Ending port forward") }
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'when local port is already bound' do
|
120
|
+
before(:each) do
|
121
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
122
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306')
|
123
|
+
forward = double(Net::SSH::Service::Forward)
|
124
|
+
@ssh.should_receive(:forward).at_least(2).and_return(forward)
|
125
|
+
forward.should_receive(:local).with(3306, '127.0.0.1', 3306).and_raise(Errno::EACCES)
|
126
|
+
forward.should_receive(:local).with(3307, '127.0.0.1', 3306)
|
127
|
+
@ssh.should_receive(:loop).and_raise(Interrupt.new)
|
128
|
+
end
|
129
|
+
it 'should bind to a higher port' do
|
130
|
+
run_output.should include("3307")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when host refuses connection' do
|
135
|
+
before(:each) do
|
136
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
137
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306')
|
138
|
+
forward = double(Net::SSH::Service::Forward)
|
139
|
+
@ssh.should_receive(:forward).and_raise(Errno::ECONNREFUSED)
|
140
|
+
end
|
141
|
+
it "should error out" do
|
142
|
+
expect { run }.to exit_with_code(0)
|
143
|
+
end
|
144
|
+
it { run_output.should include("ssh -N") }
|
145
|
+
it { run_output.should include("Error forwarding") }
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when port forwarding a scaled app with ports to forward' do
|
149
|
+
let(:haproxy_host_1) { '127.0.0.1' }
|
150
|
+
let(:haproxy_host_2) { '127.0.0.2' }
|
151
|
+
let(:mongo_host) { '51125bb94a-test907742.dev.rhcloud.com' }
|
152
|
+
let(:ipv6_host) { '::1' }
|
153
|
+
before(:each) do
|
154
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
155
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports").
|
156
|
+
and_yield(nil, :stderr, "httpd -> #{haproxy_host_1}:8080\nhttpd -> #{haproxy_host_2}:8080\nmongodb -> #{mongo_host}:35541\nmysqld -> #{ipv6_host}:3306")
|
157
|
+
forward = double(Net::SSH::Service::Forward)
|
158
|
+
@ssh.should_receive(:forward).at_least(3).times.and_return(forward)
|
159
|
+
forward.should_receive(:local).with(8080, haproxy_host_1, 8080)
|
160
|
+
forward.should_receive(:local).with(8080, haproxy_host_2, 8080).and_raise(Errno::EADDRINUSE)
|
161
|
+
forward.should_receive(:local).with(8081, haproxy_host_2, 8080)
|
162
|
+
forward.should_receive(:local).with(35541, mongo_host, 35541)
|
163
|
+
forward.should_receive(:local).with(3306, ipv6_host, 3306)
|
164
|
+
@ssh.should_receive(:loop).and_raise(Interrupt.new)
|
165
|
+
end
|
166
|
+
it "should exit when user interrupts" do
|
167
|
+
expect { run }.to exit_with_code(0)
|
168
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
169
|
+
rest_client.domains[0].applications.size.should == 1
|
170
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
171
|
+
end
|
172
|
+
it { run_output.should include("Ending port forward") }
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'when port forwarding a single gear on a scaled app' do
|
176
|
+
let(:gear_host) { 'fakesshurl.com' }
|
177
|
+
let(:gear_user) { 'fakegearid0' }
|
178
|
+
let(:arguments) { ['port-forward', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--gear', @gear_id] }
|
179
|
+
|
180
|
+
it 'should run successfully' do
|
181
|
+
@gear_id = 'fakegearid0'
|
182
|
+
Net::SSH.should_receive(:start).with(gear_host, gear_user).and_yield(@ssh).twice
|
183
|
+
|
184
|
+
@ssh.should_receive(:exec!).with("rhc-list-ports --exclude-remote").
|
185
|
+
and_yield(nil, :stderr, "mongodb -> #{gear_host}:35541")
|
186
|
+
forward = double(Net::SSH::Service::Forward)
|
187
|
+
@ssh.should_receive(:forward).and_return(forward)
|
188
|
+
forward.should_receive(:local).with(35541, gear_host, 35541)
|
189
|
+
@ssh.should_receive(:loop).and_raise(Interrupt.new)
|
190
|
+
|
191
|
+
expect { run }.to exit_with_code(0)
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should fail if the specified gear is missing' do
|
195
|
+
@gear_id = 'notarealgearxxxxx'
|
196
|
+
|
197
|
+
expect { run }.to exit_with_code(1)
|
198
|
+
run_output.should include('Gear notarealgearxxxxx not found')
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'should fail if the specified gear has no ssh info' do
|
202
|
+
@gear_id = 'fakegearid0'
|
203
|
+
|
204
|
+
# Given - gears in gear group should not have ssh info
|
205
|
+
gg = MockRestGearGroup.new(rest_client)
|
206
|
+
@app.stub(:gear_groups).and_return([gg])
|
207
|
+
gg.stub(:gears).and_return([{'state' => 'started', 'id' => 'fakegearid0'}])
|
208
|
+
|
209
|
+
expect { run }.to exit_with_code(1)
|
210
|
+
run_output.should match('The server does not support operations on individual gears.')
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rest_spec_helper'
|
3
|
+
require 'rhc/commands/scp'
|
4
|
+
|
5
|
+
describe RHC::Commands::Scp do
|
6
|
+
let!(:rest_client){ MockRestClient.new }
|
7
|
+
let!(:config){ user_config }
|
8
|
+
before{ RHC::Config.stub(:home_dir).and_return('/home/mock_user') }
|
9
|
+
before{ Kernel.stub(:exec).and_raise(RuntimeError) }
|
10
|
+
|
11
|
+
describe 'scp default' do
|
12
|
+
context 'scp' do
|
13
|
+
let(:arguments) { ['scp'] }
|
14
|
+
it { run_output.should match('Usage:') }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'scp with invalid option' do
|
19
|
+
let (:arguments) {['app', 'scp', 'app1', 'invalid_command', 'file.txt', 'app-root/data']}
|
20
|
+
|
21
|
+
context 'when run' do
|
22
|
+
before(:each) do
|
23
|
+
@domain = rest_client.add_domain("mockdomain")
|
24
|
+
@domain.add_application("app1", "mock_type")
|
25
|
+
end
|
26
|
+
it { run_output.should match("'invalid_command' is not a valid argument for this command. Please use upload or download.") }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'local file or path does not exist' do
|
31
|
+
let (:arguments) {['app', 'scp', 'app1', 'upload', 'file.txt', 'app-root/data']}
|
32
|
+
|
33
|
+
context 'when run' do
|
34
|
+
before(:each) do
|
35
|
+
@domain = rest_client.add_domain("mockdomain")
|
36
|
+
@domain.add_application("app1", "mock_type")
|
37
|
+
File.should_receive(:exist?).with("file.txt").once.and_return(false)
|
38
|
+
end
|
39
|
+
it { run_output.should match("Local file, file_path, or directory could not be found.") }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'scp connections' do
|
44
|
+
let (:arguments) {['app', 'scp', 'app1', 'upload', 'file.txt', 'app-root/data']}
|
45
|
+
|
46
|
+
context 'connection refused' do
|
47
|
+
before(:each) do
|
48
|
+
@domain = rest_client.add_domain("mockdomain")
|
49
|
+
@domain.add_application("app1", "mock_type")
|
50
|
+
File.should_receive(:exist?).with("file.txt").once.and_return(true)
|
51
|
+
Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(Errno::ECONNREFUSED)
|
52
|
+
end
|
53
|
+
it { run_output.should match("The server fakeuuidfortestsapp1 refused a connection with user 127.0.0.1. The application may be unavailable.") }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'socket error' do
|
57
|
+
before(:each) do
|
58
|
+
@domain = rest_client.add_domain("mockdomain")
|
59
|
+
@domain.add_application("app1", "mock_type")
|
60
|
+
File.should_receive(:exist?).with("file.txt").once.and_return(true)
|
61
|
+
Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(SocketError)
|
62
|
+
end
|
63
|
+
it { run_output.should match("The connection to 127.0.0.1 failed: SocketError") }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'remote file not found' do
|
67
|
+
before(:each) do
|
68
|
+
@domain = rest_client.add_domain("mockdomain")
|
69
|
+
@domain.add_application("app1", "mock_type")
|
70
|
+
File.should_receive(:exist?).with("file.txt").once.and_return(true)
|
71
|
+
Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(Net::SCP::Error)
|
72
|
+
end
|
73
|
+
it { run_output.should match("Remote file, file_path, or directory could not be found.") }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|