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,756 @@
1
+ require 'spec_helper'
2
+ require 'rest_spec_helper'
3
+ require 'rhc/wizard'
4
+ require 'rhc/vendor/parseconfig'
5
+ require 'rhc/config'
6
+ require 'ostruct'
7
+ require 'rest_spec_helper'
8
+ require 'wizard_spec_helper'
9
+ require 'tmpdir'
10
+
11
+ # Allow to define the id method
12
+ OpenStruct.__send__(:define_method, :id) { @table[:id] } if RUBY_VERSION.to_f == 1.8
13
+
14
+ describe RHC::Wizard do
15
+
16
+ def mock_config
17
+ RHC::Config.stub(:home_dir).and_return('/home/mock_user')
18
+ end
19
+
20
+ let(:options){ (o = Commander::Command::Options.new).default(default_options); o }
21
+ let(:config){ RHC::Config.new.tap{ |c| c.stub(:home_dir).and_return('/home/mock_user') } }
22
+ let(:default_options){ {} }
23
+
24
+ describe "#finalize_stage" do
25
+ subject{ described_class.new(config, options) }
26
+ before{ subject.should_receive(:say).with(/The StartApp client tools have been configured/) }
27
+ it{ subject.send(:finalize_stage).should be_true }
28
+ end
29
+
30
+ describe "#token_store" do
31
+ subject{ described_class.new(config, options) }
32
+ it{ subject.send(:token_store).should be_a(RHC::Auth::TokenStore) }
33
+ end
34
+
35
+ describe "#has_configuration?" do
36
+ subject{ described_class }
37
+ before{ File.should_receive(:exists?).with(RHC::Config.local_config_path).at_least(1).times.and_return(true) }
38
+ its(:has_configuration?){ should be_true }
39
+ end
40
+
41
+ describe "#setup_test_stage" do
42
+ subject{ described_class.new(config, options) }
43
+ it "should rescue problems" do
44
+ subject.should_receive(:all_test_methods).and_return([:_test])
45
+ subject.should_receive(:_test).and_raise(StandardError.new("An error message"))
46
+ capture{ subject.send(:setup_test_stage) }.should match "An error message"
47
+ end
48
+ end
49
+
50
+ describe "#test_private_key_mode" do
51
+ it "should raise when the file is in the wrong mode" do
52
+ FakeFS do
53
+ mock_config
54
+ FileUtils.mkdir_p(RHC::Config.ssh_dir)
55
+ File.open(RHC::Config.ssh_priv_key_file_path, 'w'){}
56
+ File.expect_mode(RHC::Config.ssh_priv_key_file_path, 0666)
57
+ expect{ subject.send(:test_private_key_mode) }.to raise_error(StandardError)
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#test_ssh_connectivity" do
63
+ subject{ described_class.new(config, options) }
64
+ let(:app) do
65
+ app = Object.new
66
+ app.should_receive(:ssh_url).at_least(1).and_return('ssh://uuid@foo.com')
67
+ app.stub(:host).and_return('foo.com')
68
+ app
69
+ end
70
+ let(:ssh) do
71
+ ssh = Object.new
72
+ ssh.should_receive(:close)
73
+ ssh
74
+ end
75
+ let(:interrupt){ Interrupt.new('interrupted') }
76
+
77
+ it "should not attempt an SSH connection" do
78
+ subject.should_receive(:ssh_key_uploaded?).and_return(true)
79
+ subject.should_receive(:applications).and_return([])
80
+ subject.send(:test_ssh_connectivity).should be_true
81
+ end
82
+ it "should attempt an SSH connection to the first app" do
83
+ subject.should_receive(:ssh_key_uploaded?).and_return(true)
84
+ subject.should_receive(:applications).and_return([app])
85
+ Net::SSH.should_receive(:start).with("foo.com", "uuid", {:timeout => 60}).and_return(ssh)
86
+ subject.send(:test_ssh_connectivity).should be_true
87
+ end
88
+ it "should handle a failed connection" do
89
+ subject.should_receive(:ssh_key_uploaded?).and_return(true)
90
+ subject.should_receive(:applications).and_return([app])
91
+ Net::SSH.should_receive(:start).and_raise(StandardError.new('an_error'))
92
+ expect{ subject.send(:test_ssh_connectivity) }.to raise_error(RuntimeError, /An SSH connection could not be established to foo.com/)
93
+ end
94
+ it "should handle an interrupted connection" do
95
+ subject.should_receive(:ssh_key_uploaded?).and_return(true)
96
+ subject.should_receive(:applications).and_return([app])
97
+ subject.should_receive(:debug_error).with(interrupt)
98
+ Net::SSH.should_receive(:start).and_raise(interrupt)
99
+ expect{ subject.send(:test_ssh_connectivity) }.to raise_error(RuntimeError, /Connection attempt to foo.com was interrupted/)
100
+ end
101
+ end
102
+
103
+ describe "#login_stage" do
104
+ let(:user){ 'test_user' }
105
+ let(:password){ 'test pass' }
106
+ let(:rest_client){ double }
107
+ let(:auth){ subject.send(:auth) }
108
+ let(:user_obj){ double(:login => user) }
109
+
110
+ subject{ described_class.new(config, options) }
111
+
112
+ def expect_client_test(with_sessions=false)
113
+ subject.should_receive(:new_client_for_options).ordered.and_return(rest_client)
114
+ rest_client.should_receive(:api).ordered
115
+ rest_client.should_receive(:user).ordered.and_return(user_obj)
116
+ rest_client.should_receive(:supports_sessions?).ordered.and_return(with_sessions)
117
+ end
118
+ def expect_raise_from_api(error)
119
+ subject.should_receive(:new_client_for_options).ordered.and_return(rest_client)
120
+ rest_client.should_receive(:api).ordered.and_raise(error)
121
+ end
122
+
123
+ it "should prompt for user and password" do
124
+ expect_client_test
125
+ subject.send(:login_stage).should be_true
126
+ subject.send(:options).rhlogin.should == user
127
+ end
128
+
129
+ context "with token" do
130
+ let(:token){ 'a_test_value' }
131
+ let(:default_options){ {:token => token, :rhlogin => user} }
132
+ before{ subject.should_receive(:say).with(/Using an existing token for #{user} to login to /).ordered }
133
+
134
+ it "should continue without prompt" do
135
+ expect_client_test
136
+ subject.send(:login_stage).should be_true
137
+ end
138
+ end
139
+
140
+ context "with credentials" do
141
+ let(:server){ mock_uri }
142
+ let(:default_options){ {:rhlogin => user, :password => password, :server => server} }
143
+ before{ subject.should_receive(:say).with(/Using #{user} to login to /).ordered }
144
+
145
+ it "should warn about a self signed cert error" do
146
+ expect_raise_from_api(RHC::Rest::SelfSignedCertificate.new('reason', 'message'))
147
+ subject.should_receive(:warn).with(/server's certificate is self-signed/).ordered
148
+ subject.should_receive(:openshift_online_server?).ordered.and_return(true)
149
+ subject.should_receive(:warn).with(/server between you and StartApp/).ordered
150
+
151
+ subject.send(:login_stage).should be_nil
152
+ end
153
+
154
+ it "should warn about a cert error for Online" do
155
+ expect_raise_from_api(RHC::Rest::CertificateVerificationFailed.new('reason', 'message'))
156
+ subject.should_receive(:warn).with(/server's certificate could not be verified/).ordered
157
+ subject.should_receive(:openshift_online_server?).ordered.and_return(true)
158
+ subject.should_receive(:warn).with(/server between you and StartApp/).ordered
159
+
160
+ subject.send(:login_stage).should be_nil
161
+ end
162
+
163
+ it "should warn about a cert error for custom server and continue" do
164
+ expect_raise_from_api(RHC::Rest::CertificateVerificationFailed.new('reason', 'message'))
165
+ subject.should_receive(:warn).with(/server's certificate could not be verified/).ordered
166
+ subject.should_receive(:openshift_online_server?).ordered.and_return(false)
167
+ subject.should_receive(:warn).with(/bypass this check/).ordered
168
+ subject.should_receive(:agree).with(/Connect without checking/).ordered.and_return(true)
169
+ expect_client_test
170
+
171
+ subject.send(:login_stage).should be_true
172
+ options.insecure.should be_true
173
+ end
174
+
175
+ it "should warn about a cert error for custom server and be cancelled" do
176
+ expect_raise_from_api(RHC::Rest::CertificateVerificationFailed.new('reason', 'message'))
177
+ subject.should_receive(:warn).with(/server's certificate could not be verified/).ordered
178
+ subject.should_receive(:openshift_online_server?).ordered.and_return(false)
179
+ subject.should_receive(:warn).with(/bypass this check/).ordered
180
+ subject.should_receive(:agree).with(/Connect without checking/).ordered.and_return(false)
181
+
182
+ subject.send(:login_stage).should be_nil
183
+ options.insecure.should be_false
184
+ end
185
+
186
+ context "when the config has enabled tokens" do
187
+ let(:default_options){ {:rhlogin => user, :password => password, :server => server, :use_authorization_tokens => true} }
188
+ let(:store){ double }
189
+ before{ RHC::Auth::TokenStore.stub(:new).and_return(store) }
190
+ before{ expect_client_test(true) }
191
+
192
+ it "should check for an existing token" do
193
+ store.should_receive(:get).and_return(nil)
194
+
195
+ subject.should_receive(:info).with(/StartApp can create and store a token on disk/).ordered
196
+ subject.should_receive(:agree).with(/Generate a token now?/).ordered.and_return(false)
197
+
198
+ subject.send(:login_stage).should be_true
199
+ options.token.should be_nil
200
+ end
201
+ end
202
+
203
+ context "with a server that supports tokens" do
204
+ before{ expect_client_test(true) }
205
+ let(:token){ 'a_test_value' }
206
+ let(:auth_token){ double(:token => token, :expires_in_seconds => 100) }
207
+ let(:store){ double }
208
+ before{ RHC::Auth::TokenStore.stub(:new).and_return(store) }
209
+
210
+ it "should not generate a token if the user does not request it" do
211
+ store.should_not_receive(:get)
212
+ subject.should_receive(:info).with(/StartApp can create and store a token on disk/).ordered
213
+ subject.should_receive(:agree).with(/Generate a token now?/).ordered.and_return(false)
214
+
215
+ subject.send(:login_stage).should be_true
216
+ options.token.should be_nil
217
+ end
218
+
219
+ it "should generate a token if the user requests it" do
220
+ store.should_not_receive(:get)
221
+ subject.should_receive(:info).with(/StartApp can create and store a token on disk/).ordered
222
+ subject.should_receive(:agree).with(/Generate a token now?/).ordered.and_return(true)
223
+ subject.should_receive(:say).with(/Generating an authorization token for this client /).ordered
224
+ rest_client.should_receive(:new_session).ordered.and_return(auth_token)
225
+ store.should_receive(:put).with(user, server, token).ordered.and_return(true)
226
+ subject.should_receive(:new_client_for_options).ordered.and_return(rest_client)
227
+ rest_client.should_receive(:user).ordered.and_return(true)
228
+ subject.should_receive(:success).with(/lasts 1 minute/).ordered
229
+
230
+ subject.send(:login_stage).should be_true
231
+ options.token.should == token
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ #TODO: Implement more stage level specs
238
+
239
+ context "when the wizard is run" do
240
+ subject{ RHC::RerunWizard.new(config, options) }
241
+
242
+ before(:each) do
243
+ mock_terminal
244
+ FakeFS.activate!
245
+ FakeFS::FileSystem.clear
246
+ mock_config
247
+ RHC::Config.initialize
248
+ end
249
+
250
+ after(:all) do
251
+ FakeFS.deactivate!
252
+ end
253
+
254
+ #after{ FileUtils.rm_rf(@tmpdir) if @tmpdir }
255
+ let(:home_dir){ '/home/mock_user' }#@tmpdir = Dir.mktmpdir }
256
+ let(:config){ RHC::Config.new.tap{ |c| c.stub(:home_dir).and_return(home_dir) } }
257
+
258
+ let(:options){ (o = Commander::Command::Options.new).default(default_options); o }
259
+ let(:default_options){ {:server => mock_uri} }
260
+ let(:username){ mock_user }
261
+ let(:password){ 'password' }
262
+ let(:user_auth){ {:user => username, :password => password} }
263
+
264
+ describe "#run" do
265
+ context "when a stage returns nil" do
266
+ before{ subject.stub(:greeting_stage).and_return(nil) }
267
+ it "should exit after that stage" do
268
+ subject.should_receive(:login_stage).never
269
+ subject.run.should be_nil
270
+ end
271
+ end
272
+ end
273
+
274
+ context "with no settings" do
275
+ before do
276
+ stub_api(false)
277
+ challenge{ stub_user }
278
+ stub_no_keys
279
+ challenge{ stub_no_domains }
280
+ stub_simple_carts(false)
281
+ end
282
+
283
+ it "should execute the minimal path" do
284
+ should_greet_user
285
+ should_challenge_for(username, password)
286
+ should_write_config
287
+ should_create_an_ssh_keypair
288
+ should_skip_uploading_key
289
+ should_find_git
290
+ should_not_find_problems
291
+ should_skip_creating_namespace
292
+ should_list_types_of_apps_to_create
293
+ should_be_done
294
+ end
295
+
296
+ context "on windows systems" do
297
+ before{ subject.stub(:windows?).and_return(true) }
298
+ it "should display windows info" do
299
+ should_greet_user
300
+ should_challenge_for(username, password)
301
+ should_write_config
302
+ should_create_an_ssh_keypair
303
+ should_skip_uploading_key
304
+ should_display_windows_info
305
+ end
306
+ end
307
+
308
+ context "when the user enters a domain and uploads a key" do
309
+ before do
310
+ stub_add_key
311
+ stub_api_request(:post, 'broker/rest/domains', user_auth).
312
+ with(:body => /(thisnamespaceistoobig|invalidnamespace)/).
313
+ to_return({
314
+ :status => 409,
315
+ :body => {
316
+ :messages => [{:field => 'id', :severity => 'ERROR', :text => 'Too long', :exit_code => 123}]
317
+ }.to_json
318
+ })
319
+ stub_create_domain('testnamespace')
320
+ end
321
+ it "should create the domain" do
322
+ should_greet_user
323
+ should_challenge_for(username, password)
324
+ should_write_config
325
+ should_create_an_ssh_keypair
326
+ should_upload_default_key
327
+ should_find_git
328
+ should_not_find_problems
329
+ should_create_a_namespace
330
+ should_list_types_of_apps_to_create
331
+ should_be_done
332
+ end
333
+ end
334
+ context "when the user inputs incorrect authentication" do
335
+ before{ stub_api_request(:get, 'broker/rest/user', :user => username, :password => 'invalid').to_return(:status => 401).times(1).to_return(simple_user(username)) }
336
+ it "should prompt them again" do
337
+ should_greet_user
338
+
339
+ input_line username
340
+ input_line 'invalid'
341
+ input_line password
342
+ next_stage.should_not be_nil
343
+
344
+ last_output do |s|
345
+ s.should match("Login to ")
346
+ s.should match("Username or password is not correct")
347
+ s.scan("Password: *").length.should == 2
348
+ end
349
+ end
350
+ end
351
+
352
+ context "when the default key is not uploaded" do
353
+ before{ stub_one_key('a'); stub_update_key('a') }
354
+ it "should prompt for the new key" do
355
+ should_greet_user
356
+ should_challenge_for(username, password)
357
+ should_write_config
358
+ should_create_an_ssh_keypair
359
+
360
+ input_line 'yes'
361
+ input_line 'a'
362
+ next_stage
363
+
364
+ last_output do |s|
365
+ s.should match(/a \(type: ssh-rsa\)/)
366
+ s.should match("Fingerprint: #{rsa_key_fingerprint_public}")
367
+ s.should match(" name |a|")
368
+ end
369
+ end
370
+ end
371
+
372
+ context "when a multiple keys exist but is not the same" do
373
+ before{ setup_mock_ssh(true) }
374
+ before do
375
+ stub_one_key('a_key')
376
+ stub_add_key_error('invalid```--', 'Invalid key name')
377
+ stub_add_key('another_key')
378
+ end
379
+ it "should give the user a name the key" do
380
+ should_greet_user
381
+ should_challenge_for(username, password)
382
+ should_write_config
383
+ should_not_create_an_ssh_keypair
384
+
385
+ input_line 'yes'
386
+ input_line 'invalid```--'
387
+ input_line 'another_key'
388
+ next_stage
389
+
390
+ last_output do |s|
391
+ s.should match(/a_key \(type: ssh-rsa\)/)
392
+ s.should match("Fingerprint: #{rsa_key_fingerprint_public}")
393
+ s.should match(" name |a_key|")
394
+ s.should match("Invalid key name")
395
+ s.should match("Uploading key 'another_key'")
396
+ end
397
+ end
398
+ end
399
+ context "when the default key already exists on the server" do
400
+ before{ setup_mock_ssh(true) }
401
+ before{ stub_mock_ssh_keys }
402
+
403
+ it "should prompt for the new key" do
404
+ should_greet_user
405
+ should_challenge_for(username, password)
406
+ should_write_config
407
+ should_not_create_an_ssh_keypair
408
+ should_find_matching_server_key
409
+ end
410
+ end
411
+ end
412
+
413
+ context "with login and existing domain and app" do
414
+ let(:default_options){ {:rhlogin => username, :server => mock_uri} }
415
+ subject{ RHC::RerunWizard.new(config, options) }
416
+
417
+ before do
418
+ stub_api false
419
+ challenge{ stub_user }
420
+ stub_no_keys
421
+ stub_add_key
422
+ stub_api_request(:post, 'broker/rest/domains', user_auth).
423
+ with(:body => /(thisnamespaceistoobig|invalidnamespace)/).
424
+ to_return({
425
+ :status => 409,
426
+ :body => {
427
+ :messages => [{:field => 'id', :severity => 'ERROR', :text => 'Too long', :exit_code => 123}]
428
+ }.to_json
429
+ })
430
+ challenge{ stub_one_domain('testnamespace') }
431
+ stub_one_application('testnamespace', 'test1')
432
+ stub_simple_carts
433
+ end
434
+
435
+ it "should skip steps that have already been completed" do
436
+ should_greet_user
437
+ should_challenge_for(nil, password)
438
+ should_write_config
439
+ should_create_an_ssh_keypair
440
+ should_upload_default_key
441
+ should_not_find_git
442
+ should_check_remote_server
443
+ should_find_a_namespace('testnamespace')
444
+ should_find_apps(['test1', 'testnamespace'])
445
+ should_be_done
446
+ end
447
+
448
+ context "with different config" do
449
+ let(:config_option){ setup_different_config }
450
+ let(:default_options){ {:rhlogin => username, :server => mock_uri, :config => config_option} }
451
+
452
+ it "should overwrite the config" do
453
+ should_greet_user
454
+ should_challenge_for(nil, password)
455
+ should_overwrite_config
456
+ end
457
+ end
458
+ end
459
+
460
+ context "with SSHWizard" do
461
+ let(:default_options){ {:rhlogin => username, :password => password} }
462
+ let(:auth){ RHC::Auth::Basic.new(options) }
463
+ let(:rest_client){ RHC::Rest::Client.new(:server => mock_uri, :auth => auth) }
464
+ subject{ RHC::SSHWizard.new(rest_client, config, options) }
465
+
466
+ before do
467
+ stub_api false
468
+ challenge{ stub_user }
469
+ end
470
+
471
+ context "with no server keys" do
472
+ before{ stub_no_keys }
473
+ before{ stub_add_key }
474
+
475
+ it "should generate and upload keys since the user does not have them" do
476
+ input_line "yes"
477
+ input_line 'default'
478
+ input_line ""
479
+
480
+ should_create_an_ssh_keypair
481
+ should_upload_default_key
482
+
483
+ #last_output.should match("Uploading key 'default'")
484
+ end
485
+
486
+ context "with default keys created" do
487
+ before{ setup_mock_ssh(true) }
488
+ it "should upload the default key" do
489
+ should_not_create_an_ssh_keypair
490
+ should_upload_default_key
491
+ end
492
+ end
493
+ end
494
+
495
+ context "with the server having the default key" do
496
+ before{ setup_mock_ssh(true) }
497
+ before{ stub_mock_ssh_keys }
498
+ it "should pass through since the user has keys already" do
499
+ subject.run.should be_true
500
+ last_output.should == ""
501
+ end
502
+ end
503
+ end
504
+
505
+ context "Check odds and ends" do
506
+ before(:each) { mock_config }
507
+ let(:wizard){ RerunWizardDriver.new }
508
+
509
+ it "should cause ssh_key_upload? to catch NoMethodError and call the fallback to get the fingerprint" do
510
+ Net::SSH::KeyFactory.should_receive(:load_public_key).exactly(4).times.and_raise(NoMethodError)
511
+ wizard.should_receive(:ssh_keygen_fallback).exactly(4).times
512
+ wizard.should_receive(:ssh_keys).at_least(1).times.and_return(wizard.get_mock_key_data)
513
+
514
+ wizard.send(:ssh_key_uploaded?)
515
+ end
516
+
517
+ it "should cause upload_ssh_key to catch NoMethodError and call the fallback to get the fingerprint" do
518
+ Net::SSH::KeyFactory.should_receive(:load_public_key).exactly(5).times.and_raise(NoMethodError)
519
+ wizard.stub(:ssh_keys).at_least(1).times.and_return(wizard.get_mock_key_data)
520
+ wizard.should_receive(:ssh_keygen_fallback).exactly(5).times.and_return(double(:name => 'default', :fingerprint => 'AA:BB:CC:DD:EE:FF', :type => 'ssh-rsa' ))
521
+
522
+ input_line 'y'
523
+
524
+ wizard.send(:upload_ssh_key_stage).should be_false
525
+
526
+ last_output.should match("Your public SSH key at .* is invalid or unreadable\.")
527
+ end
528
+
529
+ it "should cause upload_ssh_key to catch NotImplementedError and return false" do
530
+ Net::SSH::KeyFactory.should_receive(:load_public_key).exactly(5).times.and_raise(NoMethodError)
531
+ wizard.should_receive(:ssh_keys).at_least(1).times.and_return(wizard.get_mock_key_data)
532
+
533
+ input_line 'y'
534
+
535
+ wizard.send(:upload_ssh_key_stage).should be_false
536
+
537
+ output = last_output
538
+ output.should match("Your public SSH key at .* is invalid or unreadable\.")
539
+ end
540
+
541
+ it "should find a unique name" do
542
+ wizard.should_receive(:ssh_keys).at_least(1).times.and_return(wizard.get_mock_key_data)
543
+
544
+ wizard.send(:find_unique_key_name, 'cb490595').should == 'cb4905951'
545
+ wizard.send(:find_unique_key_name, 'default').should == 'default1'
546
+ wizard.send(:find_unique_key_name, 'abc').should == 'abc'
547
+ end
548
+
549
+ it "should match ssh key fallback fingerprint to net::ssh fingerprint" do
550
+ # we need to write to a live file system so ssh-keygen can find it
551
+ FakeFS.deactivate!
552
+ Dir.mktmpdir do |dir|
553
+ setup_mock_ssh_keys(dir)
554
+ pub_ssh = File.join dir, "id_rsa.pub"
555
+ fallback_fingerprint = wizard.send :ssh_keygen_fallback, pub_ssh
556
+ internal_fingerprint, short_name = wizard.get_key_fingerprint pub_ssh
557
+
558
+ fallback_fingerprint.should == internal_fingerprint
559
+ end
560
+ FakeFS.activate!
561
+ end
562
+
563
+ context "with the first run wizard" do
564
+ let(:wizard){ FirstRunWizardDriver.new }
565
+
566
+ it "prints the exception message when a domain error occurs" do
567
+ msg = "Resource conflict"
568
+ wizard.rest_client.stub(:add_domain) { raise RHC::Rest::ValidationException, msg }
569
+ input_line "testnamespace" # try to add a namespace
570
+ input_line '' # the above input will raise exception.
571
+ # we now skip configuring namespace.
572
+ wizard.send(:ask_for_namespace)
573
+ output = last_output
574
+ output.should match msg
575
+ end
576
+
577
+ it "should update the key correctly" do
578
+ key_name = 'default'
579
+ key_data = wizard.get_mock_key_data
580
+ wizard.ssh_keys = key_data
581
+ wizard.stub(:ssh_key_triple_for_default_key) { pub_key.chomp.split }
582
+ wizard.stub(:fingerprint_for_default_key) { "" } # this value is irrelevant
583
+ wizard.rest_client = double('RestClient').tap{ |o| o.stub(:find_key) { key_data.detect { |k| k.name == key_name } } }
584
+
585
+ wizard.send(:upload_ssh_key, key_name)
586
+ output = last_output
587
+ output.should match 'Updating'
588
+ end
589
+
590
+ it 'should pick a usable SSH key name' do
591
+ File.exists?('1').should be_false
592
+ key_name = 'default'
593
+ key_data = wizard.get_mock_key_data
594
+ Socket.stub(:gethostname) { key_name }
595
+ input_line("\n") # to accept default key name
596
+ wizard.ssh_keys = key_data
597
+ wizard.stub(:ssh_key_triple_for_default_key) { pub_key.chomp.split }
598
+ wizard.stub(:fingerprint_for_default_key) { "" } # this value is irrelevant
599
+ wizard.rest_client = double('RestClient').tap{ |o| o.stub(:add_key) { true } }
600
+
601
+ wizard.send(:upload_ssh_key, "other")
602
+ output = last_output
603
+ # since the clashing key name is short, we expect to present
604
+ # a key name with "1" attached to it.
605
+ output.should match "|" + key_name + "1" + "|"
606
+ File.exists?('1').should be_false
607
+ end
608
+ end
609
+ end
610
+ end
611
+
612
+ module WizardDriver
613
+
614
+ attr_accessor :mock_user, :rest_client
615
+ def initialize(*args)
616
+ if args.empty?
617
+ args = [RHC::Config.new, Commander::Command::Options.new]
618
+ args[1].default(args[0].to_options)
619
+ end
620
+ super *args
621
+ raise "No options" if options.nil?
622
+ @mock_user = 'mock_user@foo.bar'
623
+ @current_wizard_stage = nil
624
+ @platform_windows = false
625
+ #self.stub(:openshift_server).and_return('fake.foo')
626
+ end
627
+
628
+ def run_next_stage
629
+ if @current_wizard_stage.nil?
630
+ @current_wizard_stage = 0
631
+ else
632
+ return false if @current_wizard_stage >= stages.length + 1
633
+ @current_wizard_stage += 1
634
+ end
635
+
636
+ self.send stages[@current_wizard_stage]
637
+ end
638
+
639
+ # Set up @rest_client so that we can stub subsequent REST calls
640
+ def stub_rhc_client_new
641
+ @rest_client = RestSpecHelper::MockRestClient.new
642
+ end
643
+
644
+ def setup_mock_config(rhlogin=@mock_user)
645
+ FileUtils.mkdir_p File.dirname(RHC::Config.local_config_path)
646
+ File.open(RHC::Config.local_config_path, "w") do |file|
647
+ file.puts <<EOF
648
+ # Default user login
649
+ default_rhlogin='#{rhlogin}'
650
+
651
+ # Server API
652
+ libra_server = '#{openshift_server}'
653
+ EOF
654
+ end
655
+
656
+ # reload config
657
+ @config = RHC::Config.initialize
658
+ RHC::Config.ssh_dir.should =~ /mock_user/
659
+ @config.ssh_dir.should =~ /mock_user/
660
+ end
661
+
662
+ def setup_mock_domain_and_applications(domain, apps = {})
663
+ stub_rhc_client_new
664
+ apps_ary = []
665
+ apps.each do |app, url|
666
+ apps_ary.push OpenStruct.new(
667
+ :name => app,
668
+ :app_url => url == :default ? "http://#{app}-#{domain}.#{openshift_server}/" : url,
669
+ :u => true
670
+ )
671
+ end
672
+
673
+ @rest_client.stub(:domains) {
674
+ [OpenStruct.new(:id => domain, :applications => apps_ary)]
675
+ }
676
+ end
677
+
678
+ def windows=(bool)
679
+ @platform_windows = bool
680
+ end
681
+
682
+ def windows?
683
+ @platform_windows
684
+ end
685
+
686
+ def get_key_fingerprint(path=RHC::Config.ssh_pub_key_file_path)
687
+ # returns the fingerprint and the short name used as the default
688
+ # key name
689
+ fingerprint = Net::SSH::KeyFactory.load_public_key(path).fingerprint
690
+ short_name = fingerprint[0, 12].gsub(/[^0-9a-zA-Z]/,'')
691
+ return fingerprint, short_name
692
+ end
693
+
694
+ def ssh_keys=(data)
695
+ @ssh_keys = data
696
+ end
697
+
698
+ class Sshkey < OpenStruct
699
+ def update(type, content)
700
+ self.type = type
701
+ self.content = content
702
+ end
703
+ def type
704
+ @table[:type]
705
+ end
706
+ def type=(type)
707
+ @table[:type] = type
708
+ end
709
+ end
710
+
711
+ def get_mock_key_data
712
+ [
713
+ Sshkey.new(:name => 'default', :type => 'ssh-rsa', :fingerprint => "0f:97:4b:82:87:bb:c6:dc:40:a3:c1:bc:bb:55:1e:fa"),
714
+ Sshkey.new(:name => 'cb490595', :type => 'ssh-rsa', :fingerprint => "cb:49:05:95:b4:42:1c:95:74:f7:2d:41:0d:f0:37:3b"),
715
+ Sshkey.new(:name => '96d90241', :type => 'ssh-rsa', :fingerprint => "96:d9:02:41:e1:cb:0d:ce:e5:3b:fc:da:13:65:3e:32"),
716
+ Sshkey.new(:name => '73ce2cc1', :type => 'ssh-rsa', :fingerprint => "73:ce:2c:c1:01:ea:79:cc:f6:be:86:45:67:96:7f:e3")
717
+ ]
718
+ end
719
+
720
+ def config_path
721
+ config.path
722
+ end
723
+ def openshift_server
724
+ super
725
+ end
726
+ # def config(local_conf_path=nil)
727
+ # @config.set_local_config(local_conf_path, false) if local_conf_path
728
+ # @config
729
+ # end
730
+ end
731
+
732
+ class FirstRunWizardDriver < RHC::Wizard
733
+ include WizardDriver
734
+ end
735
+
736
+ class RerunWizardDriver < RHC::RerunWizard
737
+ include WizardDriver
738
+ end
739
+
740
+ class SSHWizardDriver < RHC::SSHWizard
741
+ include WizardDriver
742
+
743
+ def initialize
744
+ super RestSpecHelper::MockRestClient.new, RHC::Config.new, Commander::Command::Options.new
745
+ end
746
+ end
747
+ end
748
+
749
+ describe RHC::DomainWizard do
750
+ context "with a rest client" do
751
+ let(:rest_client){ double }
752
+ it{ described_class.new(nil, nil, rest_client).rest_client.should == rest_client }
753
+ it{ subject.stages == [:config_namespace_stage] }
754
+ it{ expect{ described_class.new(nil, nil, rest_client).send(:config_namespace, '') }.to call(:add_domain).on(rest_client).and_stop }
755
+ end
756
+ end