startapp 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +7 -0
  2. data/COPYRIGHT +1 -0
  3. data/LICENSE +11 -0
  4. data/README.md +95 -0
  5. data/Rakefile +6 -0
  6. data/autocomplete/rhc_bash +1672 -0
  7. data/bin/app +37 -0
  8. data/conf/express.conf +8 -0
  9. data/features/assets/deploy.tar.gz +0 -0
  10. data/features/core_feature.rb +191 -0
  11. data/features/deployments_feature.rb +129 -0
  12. data/features/domains_feature.rb +58 -0
  13. data/features/keys_feature.rb +37 -0
  14. data/features/members_feature.rb +166 -0
  15. data/lib/rhc/auth/basic.rb +64 -0
  16. data/lib/rhc/auth/token.rb +102 -0
  17. data/lib/rhc/auth/token_store.rb +53 -0
  18. data/lib/rhc/auth.rb +5 -0
  19. data/lib/rhc/autocomplete.rb +66 -0
  20. data/lib/rhc/autocomplete_templates/bash.erb +39 -0
  21. data/lib/rhc/cartridge_helpers.rb +118 -0
  22. data/lib/rhc/cli.rb +40 -0
  23. data/lib/rhc/command_runner.rb +185 -0
  24. data/lib/rhc/commands/account.rb +25 -0
  25. data/lib/rhc/commands/alias.rb +124 -0
  26. data/lib/rhc/commands/app.rb +726 -0
  27. data/lib/rhc/commands/apps.rb +20 -0
  28. data/lib/rhc/commands/authorization.rb +115 -0
  29. data/lib/rhc/commands/base.rb +174 -0
  30. data/lib/rhc/commands/cartridge.rb +329 -0
  31. data/lib/rhc/commands/clone.rb +66 -0
  32. data/lib/rhc/commands/configure.rb +20 -0
  33. data/lib/rhc/commands/create.rb +100 -0
  34. data/lib/rhc/commands/delete.rb +19 -0
  35. data/lib/rhc/commands/deploy.rb +32 -0
  36. data/lib/rhc/commands/deployment.rb +82 -0
  37. data/lib/rhc/commands/domain.rb +172 -0
  38. data/lib/rhc/commands/env.rb +142 -0
  39. data/lib/rhc/commands/force_stop.rb +17 -0
  40. data/lib/rhc/commands/git_clone.rb +34 -0
  41. data/lib/rhc/commands/logout.rb +51 -0
  42. data/lib/rhc/commands/logs.rb +21 -0
  43. data/lib/rhc/commands/member.rb +148 -0
  44. data/lib/rhc/commands/port_forward.rb +197 -0
  45. data/lib/rhc/commands/reload.rb +17 -0
  46. data/lib/rhc/commands/restart.rb +17 -0
  47. data/lib/rhc/commands/scp.rb +54 -0
  48. data/lib/rhc/commands/server.rb +40 -0
  49. data/lib/rhc/commands/setup.rb +60 -0
  50. data/lib/rhc/commands/show.rb +43 -0
  51. data/lib/rhc/commands/snapshot.rb +137 -0
  52. data/lib/rhc/commands/ssh.rb +51 -0
  53. data/lib/rhc/commands/sshkey.rb +97 -0
  54. data/lib/rhc/commands/start.rb +17 -0
  55. data/lib/rhc/commands/stop.rb +17 -0
  56. data/lib/rhc/commands/tail.rb +47 -0
  57. data/lib/rhc/commands/threaddump.rb +14 -0
  58. data/lib/rhc/commands/tidy.rb +17 -0
  59. data/lib/rhc/commands.rb +396 -0
  60. data/lib/rhc/config.rb +321 -0
  61. data/lib/rhc/context_helper.rb +121 -0
  62. data/lib/rhc/core_ext.rb +202 -0
  63. data/lib/rhc/coverage_helper.rb +33 -0
  64. data/lib/rhc/deployment_helpers.rb +111 -0
  65. data/lib/rhc/exceptions.rb +256 -0
  66. data/lib/rhc/git_helpers.rb +106 -0
  67. data/lib/rhc/help_formatter.rb +55 -0
  68. data/lib/rhc/helpers.rb +481 -0
  69. data/lib/rhc/highline_extensions.rb +479 -0
  70. data/lib/rhc/json.rb +51 -0
  71. data/lib/rhc/output_helpers.rb +260 -0
  72. data/lib/rhc/rest/activation.rb +11 -0
  73. data/lib/rhc/rest/alias.rb +42 -0
  74. data/lib/rhc/rest/api.rb +87 -0
  75. data/lib/rhc/rest/application.rb +348 -0
  76. data/lib/rhc/rest/attributes.rb +36 -0
  77. data/lib/rhc/rest/authorization.rb +8 -0
  78. data/lib/rhc/rest/base.rb +79 -0
  79. data/lib/rhc/rest/cartridge.rb +162 -0
  80. data/lib/rhc/rest/client.rb +650 -0
  81. data/lib/rhc/rest/deployment.rb +18 -0
  82. data/lib/rhc/rest/domain.rb +98 -0
  83. data/lib/rhc/rest/environment_variable.rb +15 -0
  84. data/lib/rhc/rest/gear_group.rb +16 -0
  85. data/lib/rhc/rest/httpclient.rb +145 -0
  86. data/lib/rhc/rest/key.rb +44 -0
  87. data/lib/rhc/rest/membership.rb +105 -0
  88. data/lib/rhc/rest/mock.rb +1042 -0
  89. data/lib/rhc/rest/user.rb +32 -0
  90. data/lib/rhc/rest.rb +148 -0
  91. data/lib/rhc/scp_helpers.rb +27 -0
  92. data/lib/rhc/ssh_helpers.rb +380 -0
  93. data/lib/rhc/tar_gz.rb +51 -0
  94. data/lib/rhc/usage_templates/command_help.erb +51 -0
  95. data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
  96. data/lib/rhc/usage_templates/help.erb +61 -0
  97. data/lib/rhc/usage_templates/missing_help.erb +1 -0
  98. data/lib/rhc/usage_templates/options_help.erb +12 -0
  99. data/lib/rhc/vendor/okjson.rb +600 -0
  100. data/lib/rhc/vendor/parseconfig.rb +178 -0
  101. data/lib/rhc/vendor/sshkey.rb +253 -0
  102. data/lib/rhc/vendor/zliby.rb +628 -0
  103. data/lib/rhc/version.rb +5 -0
  104. data/lib/rhc/wizard.rb +637 -0
  105. data/lib/rhc.rb +34 -0
  106. data/spec/coverage_helper.rb +82 -0
  107. data/spec/direct_execution_helper.rb +339 -0
  108. data/spec/keys/example.pem +23 -0
  109. data/spec/keys/example_private.pem +27 -0
  110. data/spec/keys/server.pem +19 -0
  111. data/spec/rest_spec_helper.rb +31 -0
  112. data/spec/rhc/assets/cert.crt +22 -0
  113. data/spec/rhc/assets/cert_key_rsa +27 -0
  114. data/spec/rhc/assets/empty.txt +0 -0
  115. data/spec/rhc/assets/env_vars.txt +7 -0
  116. data/spec/rhc/assets/env_vars_2.txt +1 -0
  117. data/spec/rhc/assets/foo.txt +1 -0
  118. data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
  119. data/spec/rhc/assets/targz_sample.tar.gz +0 -0
  120. data/spec/rhc/auth_spec.rb +442 -0
  121. data/spec/rhc/cli_spec.rb +186 -0
  122. data/spec/rhc/command_spec.rb +435 -0
  123. data/spec/rhc/commands/account_spec.rb +42 -0
  124. data/spec/rhc/commands/alias_spec.rb +333 -0
  125. data/spec/rhc/commands/app_spec.rb +777 -0
  126. data/spec/rhc/commands/apps_spec.rb +39 -0
  127. data/spec/rhc/commands/authorization_spec.rb +157 -0
  128. data/spec/rhc/commands/cartridge_spec.rb +665 -0
  129. data/spec/rhc/commands/clone_spec.rb +41 -0
  130. data/spec/rhc/commands/deployment_spec.rb +327 -0
  131. data/spec/rhc/commands/domain_spec.rb +401 -0
  132. data/spec/rhc/commands/env_spec.rb +493 -0
  133. data/spec/rhc/commands/git_clone_spec.rb +102 -0
  134. data/spec/rhc/commands/logout_spec.rb +86 -0
  135. data/spec/rhc/commands/member_spec.rb +247 -0
  136. data/spec/rhc/commands/port_forward_spec.rb +217 -0
  137. data/spec/rhc/commands/scp_spec.rb +77 -0
  138. data/spec/rhc/commands/server_spec.rb +69 -0
  139. data/spec/rhc/commands/setup_spec.rb +118 -0
  140. data/spec/rhc/commands/snapshot_spec.rb +179 -0
  141. data/spec/rhc/commands/ssh_spec.rb +163 -0
  142. data/spec/rhc/commands/sshkey_spec.rb +188 -0
  143. data/spec/rhc/commands/tail_spec.rb +81 -0
  144. data/spec/rhc/commands/threaddump_spec.rb +84 -0
  145. data/spec/rhc/config_spec.rb +407 -0
  146. data/spec/rhc/helpers_spec.rb +531 -0
  147. data/spec/rhc/highline_extensions_spec.rb +314 -0
  148. data/spec/rhc/json_spec.rb +30 -0
  149. data/spec/rhc/rest_application_spec.rb +258 -0
  150. data/spec/rhc/rest_client_spec.rb +752 -0
  151. data/spec/rhc/rest_spec.rb +740 -0
  152. data/spec/rhc/targz_spec.rb +55 -0
  153. data/spec/rhc/wizard_spec.rb +756 -0
  154. data/spec/spec_helper.rb +575 -0
  155. data/spec/wizard_spec_helper.rb +330 -0
  156. 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