startapp 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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