knife-winops 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +30 -0
  5. data/CHANGELOG.md +147 -0
  6. data/DOC_CHANGES.md +22 -0
  7. data/Gemfile +13 -0
  8. data/LICENSE +201 -0
  9. data/README.md +430 -0
  10. data/RELEASE_NOTES.md +17 -0
  11. data/Rakefile +21 -0
  12. data/appveyor.yml +36 -0
  13. data/ci.gemfile +15 -0
  14. data/knife-winops.gemspec +26 -0
  15. data/lib/chef/knife/bootstrap/Chef_bootstrap.erb +44 -0
  16. data/lib/chef/knife/bootstrap/bootstrap.ps1 +134 -0
  17. data/lib/chef/knife/bootstrap/tail.cmd +15 -0
  18. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +302 -0
  19. data/lib/chef/knife/bootstrap_windows_base.rb +473 -0
  20. data/lib/chef/knife/bootstrap_windows_ssh.rb +115 -0
  21. data/lib/chef/knife/bootstrap_windows_winrm.rb +102 -0
  22. data/lib/chef/knife/core/windows_bootstrap_context.rb +356 -0
  23. data/lib/chef/knife/knife_windows_base.rb +33 -0
  24. data/lib/chef/knife/windows_cert_generate.rb +155 -0
  25. data/lib/chef/knife/windows_cert_install.rb +68 -0
  26. data/lib/chef/knife/windows_helper.rb +36 -0
  27. data/lib/chef/knife/windows_listener_create.rb +107 -0
  28. data/lib/chef/knife/winrm.rb +127 -0
  29. data/lib/chef/knife/winrm_base.rb +128 -0
  30. data/lib/chef/knife/winrm_knife_base.rb +315 -0
  31. data/lib/chef/knife/winrm_session.rb +101 -0
  32. data/lib/chef/knife/winrm_shared_options.rb +54 -0
  33. data/lib/chef/knife/wsman_endpoint.rb +44 -0
  34. data/lib/chef/knife/wsman_test.rb +118 -0
  35. data/lib/knife-winops/path_helper.rb +242 -0
  36. data/lib/knife-winops/version.rb +6 -0
  37. data/spec/assets/fake_trusted_certs/excluded.txt +2 -0
  38. data/spec/assets/fake_trusted_certs/github.pem +42 -0
  39. data/spec/assets/fake_trusted_certs/google.crt +41 -0
  40. data/spec/assets/win_fake_trusted_cert_script.txt +89 -0
  41. data/spec/dummy_winrm_connection.rb +21 -0
  42. data/spec/functional/bootstrap_download_spec.rb +229 -0
  43. data/spec/spec_helper.rb +93 -0
  44. data/spec/unit/knife/bootstrap_options_spec.rb +164 -0
  45. data/spec/unit/knife/bootstrap_template_spec.rb +98 -0
  46. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +410 -0
  47. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +292 -0
  48. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -0
  49. data/spec/unit/knife/windows_cert_install_spec.rb +51 -0
  50. data/spec/unit/knife/windows_listener_create_spec.rb +76 -0
  51. data/spec/unit/knife/winrm_session_spec.rb +101 -0
  52. data/spec/unit/knife/winrm_spec.rb +494 -0
  53. data/spec/unit/knife/wsman_test_spec.rb +209 -0
  54. metadata +157 -0
@@ -0,0 +1,101 @@
1
+ #
2
+ # Original knife-windows author:: Steven Murawski <smurawski@chef.io>
3
+ # Copyright:: Copyright (c) 2015-2016 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'dummy_winrm_connection'
21
+
22
+ Chef::Knife::Winrm.load_deps
23
+
24
+
25
+ describe Chef::Knife::WinrmSession do
26
+ let(:winrm_connection) { Dummy::Connection.new }
27
+ let(:options) { { transport: :plaintext } }
28
+
29
+ before do
30
+ @original_config = Chef::Config.hash_dup
31
+ allow(WinRM::Connection).to receive(:new).and_return(winrm_connection)
32
+ end
33
+
34
+ after do
35
+ Chef::Config.configuration = @original_config
36
+ end
37
+
38
+ subject { Chef::Knife::WinrmSession.new(options) }
39
+
40
+ describe "#initialize" do
41
+ context "when a proxy is configured" do
42
+ let(:proxy_uri) { 'blah.com' }
43
+ let(:ssl_policy) { double('DefaultSSLPolicy', :set_custom_certs => nil) }
44
+
45
+ before do
46
+ Chef::Config[:http_proxy] = proxy_uri
47
+ end
48
+
49
+ it "sets the http_proxy to the configured proxy" do
50
+ subject
51
+ expect(ENV['HTTP_PROXY']).to eq("http://#{proxy_uri}")
52
+ end
53
+
54
+ it "sets the ssl policy on the winrm client" do
55
+ expect(Chef::HTTP::DefaultSSLPolicy).to receive(:new)
56
+ .with(winrm_connection.transport.httpcli.ssl_config)
57
+ .and_return(ssl_policy)
58
+ expect(ssl_policy).to receive(:set_custom_certs)
59
+ subject
60
+ end
61
+
62
+ end
63
+ end
64
+
65
+ describe "#relay_command" do
66
+ it "run command and display commands output" do
67
+ expect(winrm_connection).to receive(:shell)
68
+ subject.relay_command("cmd.exe echo 'hi'")
69
+ end
70
+
71
+ it "exits with 401 if command execution raises a 401" do
72
+ expect(winrm_connection).to receive(:shell).and_raise(WinRM::WinRMHTTPTransportError.new('', '401'))
73
+ expect { subject.relay_command("cmd.exe echo 'hi'") }.to raise_error(WinRM::WinRMHTTPTransportError)
74
+ expect(subject.exit_code).to eql(401)
75
+ end
76
+
77
+ context "cmd shell" do
78
+ before do
79
+ options[:shell] = :cmd
80
+ options[:codepage] = 65001
81
+ end
82
+
83
+ it "creates shell and sends codepage" do
84
+ expect(winrm_connection).to receive(:shell).with(:cmd, hash_including(codepage: 65001))
85
+ subject.relay_command("cmd.exe echo 'hi'")
86
+ end
87
+ end
88
+
89
+ context "powershell shell" do
90
+ before do
91
+ options[:shell] = :powershell
92
+ options[:codepage] = 65001
93
+ end
94
+
95
+ it "does not send codepage to shell" do
96
+ expect(winrm_connection).to receive(:shell).with(:powershell)
97
+ subject.relay_command("cmd.exe echo 'hi'")
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,494 @@
1
+ #
2
+ # Original knife-windows author:: Bryan McLellan <btm@chef.io>
3
+ # Copyright:: Copyright (c) 2013-2016 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'dummy_winrm_connection'
21
+
22
+ Chef::Knife::Winrm.load_deps
23
+
24
+ describe Chef::Knife::Winrm do
25
+ before do
26
+ Chef::Config.reset
27
+ end
28
+
29
+ describe "#resolve_target_nodes" do
30
+ before do
31
+ @knife = Chef::Knife::Winrm.new
32
+ @knife.config[:attribute] = "fqdn"
33
+ @node_foo = Chef::Node.new
34
+ @node_foo.automatic_attrs[:fqdn] = "foo.example.org"
35
+ @node_foo.automatic_attrs[:ipaddress] = "10.0.0.1"
36
+ @node_bar = Chef::Node.new
37
+ @node_bar.automatic_attrs[:fqdn] = "bar.example.org"
38
+ @node_bar.automatic_attrs[:ipaddress] = "10.0.0.2"
39
+ @node_bar.automatic_attrs[:ec2][:public_hostname] = "somewhere.com"
40
+ @query = double("Chef::Search::Query")
41
+ end
42
+
43
+ context "when there are some hosts found but they do not have an attribute to connect with" do
44
+ before do
45
+ @knife.config[:manual] = false
46
+ @knife.config[:winrm_password] = 'P@ssw0rd!'
47
+ allow(@query).to receive(:search).and_return([[@node_foo, @node_bar]])
48
+ @node_foo.automatic_attrs[:fqdn] = nil
49
+ @node_bar.automatic_attrs[:fqdn] = nil
50
+ allow(Chef::Search::Query).to receive(:new).and_return(@query)
51
+ end
52
+
53
+ it "raises a specific error (KNIFE-222)" do
54
+ expect(@knife.ui).to receive(:fatal).with(/does not have the required attribute/)
55
+ expect(@knife).to receive(:exit).with(10)
56
+ @knife.configure_chef
57
+ @knife.resolve_target_nodes
58
+ end
59
+ end
60
+
61
+ context "when there are nested attributes" do
62
+ before do
63
+ @knife.config[:manual] = false
64
+ @knife.config[:winrm_password] = 'P@ssw0rd!'
65
+ allow(@query).to receive(:search).and_return([[@node_foo, @node_bar]])
66
+ allow(Chef::Search::Query).to receive(:new).and_return(@query)
67
+ end
68
+
69
+ it "uses the nested attributes (KNIFE-276)" do
70
+ @knife.config[:attribute] = "ec2.public_hostname"
71
+ @knife.configure_chef
72
+ @knife.resolve_target_nodes
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "#configure_session" do
78
+ let(:winrm_user) { 'testuser' }
79
+ let(:transport) { 'plaintext' }
80
+ let(:password) { 'testpassword' }
81
+ let(:protocol) { 'basic' }
82
+ let(:knife_args) do
83
+ [
84
+ '-m', 'localhost',
85
+ '-x', winrm_user,
86
+ '-P', password,
87
+ '-w', transport,
88
+ '--winrm-authentication-protocol', protocol,
89
+ 'echo helloworld'
90
+ ]
91
+ end
92
+ let(:winrm_session) { double('winrm_session') }
93
+ let(:winrm_connection) { Dummy::Connection.new }
94
+
95
+ subject { Chef::Knife::Winrm.new(knife_args) }
96
+
97
+ context "when configuring the WinRM user name" do
98
+ context "when basic auth is used" do
99
+ let(:protocol) { 'basic' }
100
+
101
+ it "passes user name as given in options" do
102
+ expect(Chef::Knife::WinrmSession).to receive(:new) do |opts|
103
+ expect(opts[:user]).to eq(winrm_user)
104
+ end.and_return(winrm_session)
105
+ subject.configure_session
106
+ end
107
+ end
108
+
109
+ context "when negotiate auth is used" do
110
+ let(:protocol) { 'negotiate' }
111
+
112
+ context "when user is prefixed with realm" do
113
+ let(:winrm_user) { "my_realm\\myself" }
114
+
115
+ it "passes user name as given in options" do
116
+ expect(Chef::Knife::WinrmSession).to receive(:new) do |opts|
117
+ expect(opts[:user]).to eq(winrm_user)
118
+ end.and_return(winrm_session)
119
+ subject.configure_session
120
+ end
121
+ end
122
+
123
+ context "when user realm is included via email format" do
124
+ let(:winrm_user) { "myself@my_realm.com" }
125
+
126
+ it "passes user name as given in options" do
127
+ expect(Chef::Knife::WinrmSession).to receive(:new) do |opts|
128
+ expect(opts[:user]).to eq(winrm_user)
129
+ end.and_return(winrm_session)
130
+ subject.configure_session
131
+ end
132
+ end
133
+
134
+ context "when a local user is given" do
135
+ it "prefixes user with the dot (local) realm" do
136
+ expect(Chef::Knife::WinrmSession).to receive(:new) do |opts|
137
+ expect(opts[:user]).to eq(".\\#{winrm_user}")
138
+ end.and_return(winrm_session)
139
+ subject.configure_session
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ context "when configuring the WinRM password" do
146
+ it "passes password as given in options" do
147
+ expect(Chef::Knife::WinrmSession).to receive(:new) do |opts|
148
+ expect(opts[:password]).to eq(password)
149
+ end.and_return(winrm_session)
150
+ subject.configure_session
151
+ end
152
+
153
+ context "when no password is given in the options" do
154
+ let(:knife_args) do
155
+ [
156
+ '-m', 'localhost',
157
+ '-x', winrm_user,
158
+ '-w', transport,
159
+ '--winrm-authentication-protocol', protocol,
160
+ 'echo helloworld'
161
+ ]
162
+ end
163
+ let(:prompted_password) { 'prompted_password' }
164
+
165
+ before do
166
+ allow(subject.ui).to receive(:ask).and_return(prompted_password)
167
+ end
168
+
169
+ it "passes password prompted" do
170
+ expect(Chef::Knife::WinrmSession).to receive(:new) do |opts|
171
+ expect(opts[:password]).to eq(prompted_password)
172
+ end.and_return(winrm_session)
173
+ subject.configure_session
174
+ end
175
+ end
176
+ end
177
+
178
+ context "when configuring the WinRM transport" do
179
+ context "kerberos option is set" do
180
+ let(:winrm_command_http) { Chef::Knife::Winrm.new([
181
+ '-m', 'localhost',
182
+ '-x', 'testuser',
183
+ '-P', 'testpassword',
184
+ '--winrm-authentication-protocol', 'basic',
185
+ '--kerberos-realm', 'realm',
186
+ 'echo helloworld'
187
+ ]) }
188
+
189
+ it "sets the transport to kerberos" do
190
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:transport => :kerberos)).and_return(winrm_connection)
191
+ winrm_command_http.configure_chef
192
+ winrm_command_http.configure_session
193
+ end
194
+ end
195
+
196
+ context "kerberos option is set but nil" do
197
+ let(:winrm_command_http) { Chef::Knife::Winrm.new([
198
+ '-m', 'localhost',
199
+ '-x', 'testuser',
200
+ '-P', 'testpassword',
201
+ '--winrm-authentication-protocol', 'basic',
202
+ 'echo helloworld'
203
+ ]) }
204
+
205
+ it "sets the transport to plaintext" do
206
+ winrm_command_http.config[:kerberos_realm] = nil
207
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:transport => :plaintext)).and_return(winrm_connection)
208
+ winrm_command_http.configure_chef
209
+ winrm_command_http.configure_session
210
+ end
211
+ end
212
+
213
+ context "on windows workstations" do
214
+ let(:protocol) { 'negotiate' }
215
+
216
+ before do
217
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
218
+ end
219
+
220
+ it "defaults to negotiate when on a Windows host" do
221
+ expect(Chef::Knife::WinrmSession).to receive(:new) do |opts|
222
+ expect(opts[:transport]).to eq(:negotiate)
223
+ end.and_return(winrm_session)
224
+ subject.configure_session
225
+ end
226
+ end
227
+
228
+ context "on non-windows workstations" do
229
+ before do
230
+ allow(Chef::Platform).to receive(:windows?).and_return(false)
231
+ end
232
+
233
+ let(:winrm_command_http) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '-w', 'plaintext', '--winrm-authentication-protocol', 'basic', 'echo helloworld']) }
234
+
235
+ it "defaults to the http uri scheme" do
236
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :plaintext)).and_call_original
237
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:endpoint => 'http://localhost:5985/wsman')).and_return(winrm_connection)
238
+ winrm_command_http.configure_chef
239
+ winrm_command_http.configure_session
240
+ end
241
+
242
+ it "sets the operation timeout and verifes default" do
243
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:operation_timeout => 1800)).and_call_original
244
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:operation_timeout => 1800)).and_return(winrm_connection)
245
+ winrm_command_http.configure_chef
246
+ winrm_command_http.configure_session
247
+ end
248
+
249
+ it "sets the user specified winrm port" do
250
+ Chef::Config[:knife] = {winrm_port: "5988"}
251
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :plaintext)).and_call_original
252
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:transport => :plaintext)).and_return(winrm_connection)
253
+ winrm_command_http.configure_chef
254
+ winrm_command_http.configure_session
255
+ end
256
+
257
+ let(:winrm_command_timeout) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-authentication-protocol', 'basic', '--session-timeout', '10', 'echo helloworld']) }
258
+
259
+ it "sets operation timeout and verify 10 Minute timeout" do
260
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:operation_timeout => 600)).and_call_original
261
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:operation_timeout => 600)).and_return(winrm_connection)
262
+ winrm_command_timeout.configure_chef
263
+ winrm_command_timeout.configure_session
264
+ end
265
+
266
+ let(:winrm_command_https) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-transport', 'ssl', 'echo helloworld']) }
267
+
268
+ it "uses the https uri scheme if the ssl transport is specified" do
269
+ Chef::Config[:knife] = {:winrm_transport => 'ssl'}
270
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
271
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:endpoint => 'https://localhost:5986/wsman')).and_return(winrm_connection)
272
+ winrm_command_https.configure_chef
273
+ winrm_command_https.configure_session
274
+ end
275
+
276
+ it "uses the winrm port '5986' by default for ssl transport" do
277
+ Chef::Config[:knife] = {:winrm_transport => 'ssl'}
278
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
279
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:endpoint => 'https://localhost:5986/wsman')).and_return(winrm_connection)
280
+ winrm_command_https.configure_chef
281
+ winrm_command_https.configure_session
282
+ end
283
+
284
+ it "defaults to validating the server when the ssl transport is used" do
285
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
286
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:no_ssl_peer_verification => false)).and_return(winrm_connection)
287
+ winrm_command_https.configure_chef
288
+ winrm_command_https.configure_session
289
+ end
290
+
291
+ let(:winrm_command_verify_peer) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-transport', 'ssl', '--winrm-ssl-verify-mode', 'verify_peer', 'echo helloworld'])}
292
+
293
+ it "validates the server when the ssl transport is used and the :winrm_ssl_verify_mode option is not configured to :verify_none" do
294
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
295
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:no_ssl_peer_verification => false)).and_return(winrm_connection)
296
+ winrm_command_verify_peer.configure_chef
297
+ winrm_command_verify_peer.configure_session
298
+ end
299
+
300
+ context "when setting verify_none" do
301
+ let(:transport) { 'ssl' }
302
+
303
+ before { knife_args << '--winrm-ssl-verify-mode' << 'verify_none' }
304
+
305
+ it "does not validate the server when the ssl transport is used and the :winrm_ssl_verify_mode option is set to :verify_none" do
306
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
307
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:no_ssl_peer_verification => true)).and_return(winrm_connection)
308
+ subject.configure_chef
309
+ subject.configure_session
310
+ end
311
+
312
+ it "prints warning output when the :winrm_ssl_verify_mode set to :verify_none to disable server validation" do
313
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
314
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:no_ssl_peer_verification => true)).and_return(winrm_connection)
315
+ expect(subject).to receive(:warn_no_ssl_peer_verification)
316
+
317
+ subject.configure_chef
318
+ subject.configure_session
319
+ end
320
+
321
+ context "when transport is plaintext" do
322
+ let(:transport) { 'plaintext' }
323
+
324
+ it "does not print warning re ssl server validation" do
325
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :plaintext)).and_call_original
326
+ expect(WinRM::Connection).to receive(:new).and_return(winrm_connection)
327
+ expect(subject).to_not receive(:warn_no_ssl_peer_verification)
328
+
329
+ subject.configure_chef
330
+ subject.configure_session
331
+ end
332
+ end
333
+ end
334
+
335
+ let(:winrm_command_ca_trust) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-transport', 'ssl', '--ca-trust-file', '~/catrustroot', '--winrm-ssl-verify-mode', 'verify_none', 'echo helloworld'])}
336
+
337
+ it "validates the server when the ssl transport is used and the :ca_trust_file option is specified even if the :winrm_ssl_verify_mode option is set to :verify_none" do
338
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
339
+ expect(WinRM::Connection).to receive(:new).with(hash_including(:no_ssl_peer_verification => false)).and_return(winrm_connection)
340
+ winrm_command_ca_trust.configure_chef
341
+ winrm_command_ca_trust.configure_session
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ describe "#run" do
348
+ let(:session_opts) do
349
+ {
350
+ user: ".\\testuser",
351
+ password: "testpassword",
352
+ port: "5985",
353
+ transport: :plaintext,
354
+ host: "localhost"
355
+ }
356
+ end
357
+ let(:session) { Chef::Knife::WinrmSession.new(session_opts) }
358
+
359
+ before(:each) do
360
+ allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session)
361
+ Chef::Config[:knife] = {:winrm_transport => 'plaintext'}
362
+ @winrm = Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-authentication-protocol', 'basic', 'echo helloworld'])
363
+ end
364
+
365
+ it "returns with 0 if the command succeeds" do
366
+ allow(@winrm).to receive(:relay_winrm_command).and_return(0)
367
+ return_code = @winrm.run
368
+ expect(return_code).to be_zero
369
+ end
370
+
371
+ it "exits with exact exit status if the command fails and returns config is set to 0" do
372
+ command_status = 510
373
+
374
+ @winrm.config[:returns] = "0"
375
+ Chef::Config[:knife][:returns] = [0]
376
+
377
+ allow(@winrm).to receive(:relay_winrm_command)
378
+ allow(@winrm.ui).to receive(:error)
379
+ allow(session).to receive(:exit_code).and_return(command_status)
380
+ expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(command_status) }
381
+ end
382
+
383
+ it "exits with non-zero status if the command fails and returns config is set to 0" do
384
+ command_status = 1
385
+ @winrm.config[:returns] = "0,53"
386
+ Chef::Config[:knife][:returns] = [0,53]
387
+ allow(@winrm).to receive(:relay_winrm_command).and_return(command_status)
388
+ allow(@winrm.ui).to receive(:error)
389
+ allow(session).to receive(:exit_code).and_return(command_status)
390
+ expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(command_status) }
391
+ end
392
+
393
+ it "exits with a zero status if the command returns an expected non-zero status" do
394
+ command_status = 53
395
+ Chef::Config[:knife][:returns] = [0,53]
396
+ allow(@winrm).to receive(:relay_winrm_command).and_return(command_status)
397
+ allow(session).to receive(:exit_codes).and_return({"thishost" => command_status})
398
+ exit_code = @winrm.run
399
+ expect(exit_code).to be_zero
400
+ end
401
+
402
+ it "exits with a zero status if the command returns an expected non-zero status" do
403
+ command_status = 53
404
+ @winrm.config[:returns] = '0,53'
405
+ allow(@winrm).to receive(:relay_winrm_command).and_return(command_status)
406
+ allow(session).to receive(:exit_codes).and_return({"thishost" => command_status})
407
+ exit_code = @winrm.run
408
+ expect(exit_code).to be_zero
409
+ end
410
+
411
+ it "exits with 100 and no hint if command execution raises an exception other than 401" do
412
+ allow(@winrm).to receive(:relay_winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new('', '500'))
413
+ allow(@winrm.ui).to receive(:error)
414
+ expect(@winrm.ui).to_not receive(:info)
415
+ expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(100) }
416
+ end
417
+
418
+ it "exits with 100 if command execution raises a 401" do
419
+ allow(@winrm).to receive(:relay_winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new('', '401'))
420
+ allow(@winrm.ui).to receive(:info)
421
+ allow(@winrm.ui).to receive(:error)
422
+ expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(100) }
423
+ end
424
+
425
+ it "prints a hint on failure for negotiate authentication" do
426
+ @winrm.config[:winrm_authentication_protocol] = "negotiate"
427
+ @winrm.config[:winrm_transport] = "plaintext"
428
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
429
+ allow(session).to receive(:relay_command).and_raise(WinRM::WinRMAuthorizationError.new)
430
+ allow(@winrm.ui).to receive(:error)
431
+ allow(@winrm.ui).to receive(:info)
432
+ expect(@winrm.ui).to receive(:info).with(Chef::Knife::Winrm::FAILED_NOT_BASIC_HINT)
433
+ expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit)
434
+ end
435
+
436
+ it "prints a hint on failure for basic authentication" do
437
+ @winrm.config[:winrm_authentication_protocol] = "basic"
438
+ @winrm.config[:winrm_transport] = "plaintext"
439
+ allow(session).to receive(:relay_command).and_raise(WinRM::WinRMHTTPTransportError.new('', '401'))
440
+ allow(@winrm.ui).to receive(:error)
441
+ allow(@winrm.ui).to receive(:info)
442
+ expect(@winrm.ui).to receive(:info).with(Chef::Knife::Winrm::FAILED_BASIC_HINT)
443
+ expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit)
444
+ end
445
+
446
+ context "when winrm_authentication_protocol specified" do
447
+ before do
448
+ Chef::Config[:knife] = {:winrm_transport => 'plaintext'}
449
+ allow(@winrm).to receive(:relay_winrm_command).and_return(0)
450
+ end
451
+
452
+ it "sets negotiate transport on windows for 'negotiate' authentication" do
453
+ @winrm.config[:winrm_authentication_protocol] = "negotiate"
454
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
455
+ allow(Chef::Knife::WinrmSession).to receive(:new) do |opts|
456
+ expect(opts[:disable_sspi]).to be(false)
457
+ expect(opts[:transport]).to be(:negotiate)
458
+ end.and_return(session)
459
+ @winrm.run
460
+ end
461
+
462
+ it "sets negotiate transport on unix for 'negotiate' authentication" do
463
+ @winrm.config[:winrm_authentication_protocol] = "negotiate"
464
+ allow(Chef::Platform).to receive(:windows?).and_return(false)
465
+ allow(Chef::Knife::WinrmSession).to receive(:new) do |opts|
466
+ expect(opts[:disable_sspi]).to be(false)
467
+ expect(opts[:transport]).to be(:negotiate)
468
+ end.and_return(session)
469
+ @winrm.run
470
+ end
471
+
472
+ it "disables sspi and skips the winrm monkey patch for 'ssl' transport and 'basic' authentication" do
473
+ @winrm.config[:winrm_authentication_protocol] = "basic"
474
+ @winrm.config[:winrm_transport] = "ssl"
475
+ @winrm.config[:winrm_port] = "5986"
476
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
477
+ allow(Chef::Knife::WinrmSession).to receive(:new) do |opts|
478
+ expect(opts[:port]).to be(@winrm.config[:winrm_port])
479
+ expect(opts[:transport]).to be(:ssl)
480
+ expect(opts[:disable_sspi]).to be(true)
481
+ expect(opts[:basic_auth_only]).to be(true)
482
+ end.and_return(session)
483
+ @winrm.run
484
+ end
485
+
486
+ it "raises an error if value is other than [basic, negotiate, kerberos]" do
487
+ @winrm.config[:winrm_authentication_protocol] = "invalid"
488
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
489
+ expect(@winrm.ui).to receive(:error)
490
+ expect { @winrm.run }.to raise_error(SystemExit)
491
+ end
492
+ end
493
+ end
494
+ end