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,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