knife-windows 1.2.0 → 1.2.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -5
  3. data/.travis.yml +23 -23
  4. data/CHANGELOG.md +104 -101
  5. data/DOC_CHANGES.md +14 -14
  6. data/Gemfile +12 -12
  7. data/LICENSE +201 -201
  8. data/README.md +376 -376
  9. data/RELEASE_NOTES.md +34 -34
  10. data/Rakefile +21 -21
  11. data/appveyor.yml +42 -42
  12. data/ci.gemfile +15 -15
  13. data/features/knife_help.feature +20 -20
  14. data/features/support/env.rb +5 -5
  15. data/knife-windows.gemspec +25 -26
  16. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +247 -247
  17. data/lib/chef/knife/bootstrap_windows_base.rb +415 -415
  18. data/lib/chef/knife/bootstrap_windows_ssh.rb +115 -115
  19. data/lib/chef/knife/bootstrap_windows_winrm.rb +95 -95
  20. data/lib/chef/knife/core/windows_bootstrap_context.rb +366 -362
  21. data/lib/chef/knife/knife_windows_base.rb +33 -33
  22. data/lib/chef/knife/windows_cert_generate.rb +155 -155
  23. data/lib/chef/knife/windows_cert_install.rb +68 -68
  24. data/lib/chef/knife/windows_helper.rb +36 -36
  25. data/lib/chef/knife/windows_listener_create.rb +107 -107
  26. data/lib/chef/knife/winrm.rb +122 -122
  27. data/lib/chef/knife/winrm_base.rb +113 -113
  28. data/lib/chef/knife/winrm_knife_base.rb +298 -298
  29. data/lib/chef/knife/winrm_session.rb +86 -86
  30. data/lib/chef/knife/winrm_shared_options.rb +47 -47
  31. data/lib/chef/knife/wsman_endpoint.rb +44 -44
  32. data/lib/chef/knife/wsman_test.rb +117 -95
  33. data/lib/knife-windows/path_helper.rb +234 -234
  34. data/lib/knife-windows/version.rb +6 -6
  35. data/spec/assets/win_template_rendered_with_bootstrap_install_command.txt +217 -217
  36. data/spec/assets/win_template_rendered_with_bootstrap_install_command_on_12_5_client.txt +217 -217
  37. data/spec/assets/win_template_rendered_without_bootstrap_install_command.txt +329 -329
  38. data/spec/assets/win_template_rendered_without_bootstrap_install_command_on_12_5_client.txt +329 -329
  39. data/spec/assets/win_template_unrendered.txt +246 -246
  40. data/spec/functional/bootstrap_download_spec.rb +234 -234
  41. data/spec/spec_helper.rb +93 -93
  42. data/spec/unit/knife/bootstrap_options_spec.rb +154 -150
  43. data/spec/unit/knife/bootstrap_template_spec.rb +92 -92
  44. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +295 -295
  45. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +177 -151
  46. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -90
  47. data/spec/unit/knife/windows_cert_install_spec.rb +51 -51
  48. data/spec/unit/knife/windows_listener_create_spec.rb +76 -76
  49. data/spec/unit/knife/winrm_session_spec.rb +64 -64
  50. data/spec/unit/knife/winrm_spec.rb +516 -516
  51. data/spec/unit/knife/wsman_test_spec.rb +201 -178
  52. metadata +2 -16
@@ -1,92 +1,92 @@
1
- #
2
- # Author:: Chirag Jog (<chirag@clogeny.com>)
3
- # Copyright:: Copyright (c) 2013 Chirag Jog
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
- TEMPLATE_FILE = File.expand_path(File.dirname(__FILE__)) + "/../../../lib/chef/knife/bootstrap/windows-chef-client-msi.erb"
20
-
21
- require 'spec_helper'
22
-
23
- describe Chef::Knife::BootstrapWindowsWinrm do
24
- let(:template_file) { TEMPLATE_FILE }
25
- let(:options) { [] }
26
- let(:rendered_template) do
27
- knife.instance_variable_set("@template_file", template_file)
28
- knife.parse_options(options)
29
- # Avoid referencing a validation keyfile we won't find during #render_template
30
- template = IO.read(template_file).chomp
31
- knife.render_template(template)
32
- end
33
-
34
- before(:all) do
35
- @original_config = Chef::Config.hash_dup
36
- @original_knife_config = Chef::Config[:knife].dup
37
- end
38
-
39
- after(:all) do
40
- Chef::Config.configuration = @original_config
41
- Chef::Config[:knife] = @original_knife_config
42
- end
43
-
44
- before(:each) do
45
- Chef::Log.logger = Logger.new(StringIO.new)
46
- @knife = Chef::Knife::BootstrapWindowsWinrm.new
47
- # Merge default settings in.
48
- @knife.merge_configs
49
- @knife.config[:template_file] = template_file
50
- @stdout = StringIO.new
51
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
52
- @stderr = StringIO.new
53
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
54
- end
55
-
56
- describe "specifying no_proxy with various entries" do
57
- subject(:knife) { described_class.new }
58
- let(:options){ ["--bootstrap-proxy", "", "--bootstrap-no-proxy", setting] }
59
-
60
- context "via --bootstrap-no-proxy" do
61
- let(:setting) { "api.opscode.com" }
62
-
63
- it "renders the client.rb with a single FQDN no_proxy entry" do
64
- expect(rendered_template).to match(%r{.*no_proxy\s*\"api.opscode.com\".*})
65
- end
66
- end
67
- context "via --bootstrap-no-proxy multiple" do
68
- let(:setting) { "api.opscode.com,172.16.10.*" }
69
-
70
- it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do
71
- expect(rendered_template).to match(%r{.*no_proxy\s*"api.opscode.com,172.16.10.\*".*})
72
- end
73
- end
74
- end
75
-
76
- describe "specifying --msi-url" do
77
- subject(:knife) { described_class.new }
78
-
79
- context "with explicitly provided --msi-url" do
80
- let(:options) { ["--msi-url", "file:///something.msi"] }
81
-
82
- it "bootstrap batch file must fetch from provided url" do
83
- expect(rendered_template).to match(%r{.*REMOTE_SOURCE_MSI_URL=file:///something\.msi.*})
84
- end
85
- end
86
- context "with no provided --msi-url" do
87
- it "bootstrap batch file must fetch from provided url" do
88
- expect(rendered_template).to match(%r{.*REMOTE_SOURCE_MSI_URL=https://www\.chef\.io/.*})
89
- end
90
- end
91
- end
92
- end
1
+ #
2
+ # Author:: Chirag Jog (<chirag@clogeny.com>)
3
+ # Copyright:: Copyright (c) 2013 Chirag Jog
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
+ TEMPLATE_FILE = File.expand_path(File.dirname(__FILE__)) + "/../../../lib/chef/knife/bootstrap/windows-chef-client-msi.erb"
20
+
21
+ require 'spec_helper'
22
+
23
+ describe Chef::Knife::BootstrapWindowsWinrm do
24
+ let(:template_file) { TEMPLATE_FILE }
25
+ let(:options) { [] }
26
+ let(:rendered_template) do
27
+ knife.instance_variable_set("@template_file", template_file)
28
+ knife.parse_options(options)
29
+ # Avoid referencing a validation keyfile we won't find during #render_template
30
+ template = IO.read(template_file).chomp
31
+ knife.render_template(template)
32
+ end
33
+
34
+ before(:all) do
35
+ @original_config = Chef::Config.hash_dup
36
+ @original_knife_config = Chef::Config[:knife].dup
37
+ end
38
+
39
+ after(:all) do
40
+ Chef::Config.configuration = @original_config
41
+ Chef::Config[:knife] = @original_knife_config
42
+ end
43
+
44
+ before(:each) do
45
+ Chef::Log.logger = Logger.new(StringIO.new)
46
+ @knife = Chef::Knife::BootstrapWindowsWinrm.new
47
+ # Merge default settings in.
48
+ @knife.merge_configs
49
+ @knife.config[:template_file] = template_file
50
+ @stdout = StringIO.new
51
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
52
+ @stderr = StringIO.new
53
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
54
+ end
55
+
56
+ describe "specifying no_proxy with various entries" do
57
+ subject(:knife) { described_class.new }
58
+ let(:options){ ["--bootstrap-proxy", "", "--bootstrap-no-proxy", setting] }
59
+
60
+ context "via --bootstrap-no-proxy" do
61
+ let(:setting) { "api.opscode.com" }
62
+
63
+ it "renders the client.rb with a single FQDN no_proxy entry" do
64
+ expect(rendered_template).to match(%r{.*no_proxy\s*\"api.opscode.com\".*})
65
+ end
66
+ end
67
+ context "via --bootstrap-no-proxy multiple" do
68
+ let(:setting) { "api.opscode.com,172.16.10.*" }
69
+
70
+ it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do
71
+ expect(rendered_template).to match(%r{.*no_proxy\s*"api.opscode.com,172.16.10.\*".*})
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "specifying --msi-url" do
77
+ subject(:knife) { described_class.new }
78
+
79
+ context "with explicitly provided --msi-url" do
80
+ let(:options) { ["--msi-url", "file:///something.msi"] }
81
+
82
+ it "bootstrap batch file must fetch from provided url" do
83
+ expect(rendered_template).to match(%r{.*REMOTE_SOURCE_MSI_URL=file:///something\.msi.*})
84
+ end
85
+ end
86
+ context "with no provided --msi-url" do
87
+ it "bootstrap batch file must fetch from provided url" do
88
+ expect(rendered_template).to match(%r{.*REMOTE_SOURCE_MSI_URL=https://www\.chef\.io/.*})
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,295 +1,295 @@
1
- #
2
- # Author:: Adam Edwards(<adamed@getchef.com>)
3
- # Copyright:: Copyright (c) 2014 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
-
21
- Chef::Knife::Winrm.load_deps
22
-
23
- describe Chef::Knife::BootstrapWindowsWinrm do
24
- before(:all) do
25
- Chef::Config.reset
26
- end
27
-
28
- before do
29
- bootstrap.config[:run_list] = []
30
- allow(bootstrap).to receive(:validate_options!).and_return(nil)
31
- allow(bootstrap).to receive(:sleep).and_return(10)
32
- allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session)
33
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
34
- end
35
-
36
- after do
37
- allow(bootstrap).to receive(:sleep).and_return(10)
38
- end
39
-
40
- let(:session_opts) do
41
- {
42
- user: "Administrator",
43
- password: "testpassword",
44
- port: "5986",
45
- transport: :ssl,
46
- host: "localhost"
47
- }
48
- end
49
- let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['winrm', '-d', 'windows-chef-client-msi', '-x', session_opts[:user], '-P', session_opts[:password], session_opts[:host]]) }
50
- let(:session) { Chef::Knife::WinrmSession.new(session_opts) }
51
- let(:initial_fail_count) { 4 }
52
-
53
- context "knife secret-file && knife secret options are passed" do
54
- before do
55
- Chef::Config.reset
56
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
57
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
58
- end
59
- it "gives preference to secret key passed under knife's secret-file option" do
60
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
61
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
62
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
63
- expect(bootstrap.load_correct_secret).to eq(
64
- "data_bag_secret_key_passed_under_knife_secret_file_option")
65
- end
66
- end
67
-
68
- context "cli secret-file && cli secret options are passed" do
69
- before do
70
- Chef::Config.reset
71
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
72
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
73
- end
74
- it "gives preference to secret key passed under cli's secret-file option" do
75
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
76
- bootstrap.config[:encrypted_data_bag_secret_file]).
77
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
78
- expect(bootstrap.load_correct_secret).to eq(
79
- "data_bag_secret_key_passed_under_cli_secret_file_option")
80
- end
81
- end
82
-
83
- context "knife secret-file, knife secret, cli secret-file && cli secret options are passed" do
84
- before do
85
- Chef::Config.reset
86
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/knife_encrypted_data_bag_secret"
87
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
88
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/cli_encrypted_data_bag_secret"
89
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
90
- end
91
- it "gives preference to secret key passed under cli's secret-file option" do
92
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
93
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
94
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
95
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
96
- bootstrap.config[:encrypted_data_bag_secret_file]).
97
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
98
- expect(bootstrap.load_correct_secret).to eq(
99
- "data_bag_secret_key_passed_under_cli_secret_file_option")
100
- end
101
- end
102
-
103
- context "knife secret-file && cli secret options are passed" do
104
- before do
105
- Chef::Config.reset
106
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
107
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
108
- end
109
- it "gives preference to secret key passed under cli's secret option" do
110
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
111
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
112
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
113
- expect(bootstrap.load_correct_secret).to eq(
114
- "data_bag_secret_key_passed_under_cli_secret_option")
115
- end
116
- end
117
-
118
- context "knife secret && cli secret-file options are passed" do
119
- before do
120
- Chef::Config.reset
121
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
122
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
123
- end
124
- it "gives preference to secret key passed under cli's secret-file option" do
125
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
126
- bootstrap.config[:encrypted_data_bag_secret_file]).
127
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
128
- expect(bootstrap.load_correct_secret).to eq(
129
- "data_bag_secret_key_passed_under_cli_secret_file_option")
130
- end
131
- end
132
-
133
- context "cli secret-file option is passed" do
134
- before do
135
- Chef::Config.reset
136
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
137
- end
138
- it "takes the secret key passed under cli's secret-file option" do
139
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
140
- bootstrap.config[:encrypted_data_bag_secret_file]).
141
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
142
- expect(bootstrap.load_correct_secret).to eq(
143
- "data_bag_secret_key_passed_under_cli_secret_file_option")
144
- end
145
- end
146
-
147
- it 'should pass exit code from failed winrm call' do
148
- allow(session).to receive(:exit_code).and_return(500)
149
- allow(bootstrap).to receive(:wait_for_remote_response)
150
- allow(bootstrap).to receive(:create_bootstrap_bat_command)
151
- allow(session).to receive(:relay_command)
152
- allow(bootstrap.ui).to receive(:info)
153
- expect { bootstrap.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(500) }
154
- end
155
-
156
- it 'should retry if a 401 is received from WinRM' do
157
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '401')}}
158
- call_result_sequence.push(0)
159
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
160
- allow(bootstrap).to receive(:print)
161
- allow(bootstrap.ui).to receive(:info)
162
-
163
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
164
- bootstrap.send(:wait_for_remote_response, 2)
165
- end
166
-
167
- it 'should retry if something other than a 401 is received from WinRM' do
168
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
169
- call_result_sequence.push(0)
170
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
171
- allow(bootstrap).to receive(:print)
172
- allow(bootstrap.ui).to receive(:info)
173
-
174
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
175
- bootstrap.send(:wait_for_remote_response, 2)
176
- end
177
-
178
- it 'should keep retrying at 10s intervals if the timeout in minutes has not elapsed' do
179
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
180
- call_result_sequence.push(0)
181
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
182
- allow(bootstrap).to receive(:print)
183
- allow(bootstrap.ui).to receive(:info)
184
-
185
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
186
- bootstrap.send(:wait_for_remote_response, 2)
187
- end
188
-
189
- it 'should have a wait timeout of 2 minutes by default' do
190
- allow(bootstrap).to receive(:run_command).and_raise(WinRM::WinRMHTTPTransportError.new('','500'))
191
- allow(bootstrap).to receive(:create_bootstrap_bat_command).and_raise(SystemExit)
192
- expect(bootstrap).to receive(:wait_for_remote_response).with(2)
193
- allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
194
-
195
- allow(bootstrap.ui).to receive(:info)
196
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
197
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
198
- end
199
-
200
- it 'should not a wait for timeout on Errno::ECONNREFUSED' do
201
- allow(bootstrap).to receive(:run_command).and_raise(Errno::ECONNREFUSED.new)
202
- allow(bootstrap.ui).to receive(:info)
203
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
204
- expect(bootstrap.ui).to receive(:error).with("Connection refused connecting to localhost:5985.")
205
-
206
- # wait_for_remote_response is protected method, So define singleton test method to call it.
207
- bootstrap.define_singleton_method(:test_wait_for_remote_response){wait_for_remote_response(bootstrap.options[:auth_timeout][:default])}
208
- expect { bootstrap.test_wait_for_remote_response }.to raise_error(Errno::ECONNREFUSED)
209
- end
210
-
211
- it 'should stop retrying if more than 2 minutes has elapsed' do
212
- times = [ Time.new(2014, 4, 1, 22, 25), Time.new(2014, 4, 1, 22, 51), Time.new(2014, 4, 1, 22, 28) ]
213
- allow(Time).to receive(:now).and_return(*times)
214
- run_command_result = lambda {raise WinRM::WinRMHTTPTransportError, '401'}
215
- allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
216
- allow(bootstrap).to receive(:run_command).and_return(run_command_result)
217
- allow(bootstrap).to receive(:print)
218
- allow(bootstrap.ui).to receive(:info)
219
- allow(bootstrap.ui).to receive(:error)
220
- expect(bootstrap).to receive(:run_command).exactly(1).times
221
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
222
- expect { bootstrap.bootstrap }.to raise_error RuntimeError
223
- end
224
-
225
- context "when validation_key is not present" do
226
- context "using chef 11", :chef_lt_12_only do
227
- before do
228
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
229
- end
230
-
231
- it 'raises an exception if validation_key is not present in chef 11' do
232
- expect(bootstrap.ui).to receive(:error)
233
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
234
- end
235
- end
236
-
237
- context "using chef 12", :chef_gte_12_only do
238
- before do
239
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
240
- bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
241
- Chef::Config[:knife] = {:chef_node_name => 'foo.example.com'}
242
- end
243
-
244
- it 'raises an exception if winrm_authentication_protocol is basic and transport is plaintext' do
245
- Chef::Config[:knife] = {:winrm_authentication_protocol => 'basic', :winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com'}
246
- expect(bootstrap.ui).to receive(:error)
247
- expect { bootstrap.run }.to raise_error(SystemExit)
248
- end
249
-
250
- it 'raises an exception if chef_node_name is not present ' do
251
- Chef::Config[:knife] = {:chef_node_name => nil}
252
- expect(bootstrap.client_builder).not_to receive(:run)
253
- expect(bootstrap.client_builder).not_to receive(:client_path)
254
- expect(bootstrap.ui).to receive(:error)
255
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
256
- end
257
- end
258
- end
259
-
260
- context "when doing chef vault", :chef_gte_12_only do
261
- let(:vault_handler) { double('vault_handler', :doing_chef_vault? => true) }
262
- let(:node_name) { 'foo.example.com' }
263
- before do
264
- allow(bootstrap).to receive(:wait_for_remote_response)
265
- allow(bootstrap).to receive(:create_bootstrap_bat_command)
266
- allow(bootstrap).to receive(:run_command).and_return(0)
267
- bootstrap.config[:chef_node_name] = node_name
268
- bootstrap.chef_vault_handler = vault_handler
269
- end
270
-
271
- context "builder does not respond to client" do
272
- before do
273
- bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
274
- end
275
-
276
- it "passes a node search query to the handler" do
277
- expect(vault_handler).to receive(:run).with(node_name: node_name)
278
- bootstrap.bootstrap
279
- end
280
- end
281
-
282
- context "builder responds to client" do
283
- let(:client) { Chef::ApiClient.new }
284
-
285
- before do
286
- bootstrap.client_builder = double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil, :client => client)
287
- end
288
-
289
- it "passes a node search query to the handler" do
290
- expect(vault_handler).to receive(:run).with(client)
291
- bootstrap.bootstrap
292
- end
293
- end
294
- end
295
- end
1
+ #
2
+ # Author:: Adam Edwards(<adamed@getchef.com>)
3
+ # Copyright:: Copyright (c) 2014 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
+
21
+ Chef::Knife::Winrm.load_deps
22
+
23
+ describe Chef::Knife::BootstrapWindowsWinrm do
24
+ before(:all) do
25
+ Chef::Config.reset
26
+ end
27
+
28
+ before do
29
+ bootstrap.config[:run_list] = []
30
+ allow(bootstrap).to receive(:validate_options!).and_return(nil)
31
+ allow(bootstrap).to receive(:sleep).and_return(10)
32
+ allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session)
33
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
34
+ end
35
+
36
+ after do
37
+ allow(bootstrap).to receive(:sleep).and_return(10)
38
+ end
39
+
40
+ let(:session_opts) do
41
+ {
42
+ user: "Administrator",
43
+ password: "testpassword",
44
+ port: "5986",
45
+ transport: :ssl,
46
+ host: "localhost"
47
+ }
48
+ end
49
+ let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['winrm', '-d', 'windows-chef-client-msi', '-x', session_opts[:user], '-P', session_opts[:password], session_opts[:host]]) }
50
+ let(:session) { Chef::Knife::WinrmSession.new(session_opts) }
51
+ let(:initial_fail_count) { 4 }
52
+
53
+ context "knife secret-file && knife secret options are passed" do
54
+ before do
55
+ Chef::Config.reset
56
+ Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
57
+ Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
58
+ end
59
+ it "gives preference to secret key passed under knife's secret-file option" do
60
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
61
+ Chef::Config[:knife][:encrypted_data_bag_secret_file]).
62
+ and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
63
+ expect(bootstrap.load_correct_secret).to eq(
64
+ "data_bag_secret_key_passed_under_knife_secret_file_option")
65
+ end
66
+ end
67
+
68
+ context "cli secret-file && cli secret options are passed" do
69
+ before do
70
+ Chef::Config.reset
71
+ bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
72
+ bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
73
+ end
74
+ it "gives preference to secret key passed under cli's secret-file option" do
75
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
76
+ bootstrap.config[:encrypted_data_bag_secret_file]).
77
+ and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
78
+ expect(bootstrap.load_correct_secret).to eq(
79
+ "data_bag_secret_key_passed_under_cli_secret_file_option")
80
+ end
81
+ end
82
+
83
+ context "knife secret-file, knife secret, cli secret-file && cli secret options are passed" do
84
+ before do
85
+ Chef::Config.reset
86
+ Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/knife_encrypted_data_bag_secret"
87
+ Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
88
+ bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/cli_encrypted_data_bag_secret"
89
+ bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
90
+ end
91
+ it "gives preference to secret key passed under cli's secret-file option" do
92
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
93
+ Chef::Config[:knife][:encrypted_data_bag_secret_file]).
94
+ and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
95
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
96
+ bootstrap.config[:encrypted_data_bag_secret_file]).
97
+ and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
98
+ expect(bootstrap.load_correct_secret).to eq(
99
+ "data_bag_secret_key_passed_under_cli_secret_file_option")
100
+ end
101
+ end
102
+
103
+ context "knife secret-file && cli secret options are passed" do
104
+ before do
105
+ Chef::Config.reset
106
+ Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
107
+ bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
108
+ end
109
+ it "gives preference to secret key passed under cli's secret option" do
110
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
111
+ Chef::Config[:knife][:encrypted_data_bag_secret_file]).
112
+ and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
113
+ expect(bootstrap.load_correct_secret).to eq(
114
+ "data_bag_secret_key_passed_under_cli_secret_option")
115
+ end
116
+ end
117
+
118
+ context "knife secret && cli secret-file options are passed" do
119
+ before do
120
+ Chef::Config.reset
121
+ Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
122
+ bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
123
+ end
124
+ it "gives preference to secret key passed under cli's secret-file option" do
125
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
126
+ bootstrap.config[:encrypted_data_bag_secret_file]).
127
+ and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
128
+ expect(bootstrap.load_correct_secret).to eq(
129
+ "data_bag_secret_key_passed_under_cli_secret_file_option")
130
+ end
131
+ end
132
+
133
+ context "cli secret-file option is passed" do
134
+ before do
135
+ Chef::Config.reset
136
+ bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
137
+ end
138
+ it "takes the secret key passed under cli's secret-file option" do
139
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
140
+ bootstrap.config[:encrypted_data_bag_secret_file]).
141
+ and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
142
+ expect(bootstrap.load_correct_secret).to eq(
143
+ "data_bag_secret_key_passed_under_cli_secret_file_option")
144
+ end
145
+ end
146
+
147
+ it 'should pass exit code from failed winrm call' do
148
+ allow(session).to receive(:exit_code).and_return(500)
149
+ allow(bootstrap).to receive(:wait_for_remote_response)
150
+ allow(bootstrap).to receive(:create_bootstrap_bat_command)
151
+ allow(session).to receive(:relay_command)
152
+ allow(bootstrap.ui).to receive(:info)
153
+ expect { bootstrap.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(500) }
154
+ end
155
+
156
+ it 'should retry if a 401 is received from WinRM' do
157
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '401')}}
158
+ call_result_sequence.push(0)
159
+ allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
160
+ allow(bootstrap).to receive(:print)
161
+ allow(bootstrap.ui).to receive(:info)
162
+
163
+ expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
164
+ bootstrap.send(:wait_for_remote_response, 2)
165
+ end
166
+
167
+ it 'should retry if something other than a 401 is received from WinRM' do
168
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
169
+ call_result_sequence.push(0)
170
+ allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
171
+ allow(bootstrap).to receive(:print)
172
+ allow(bootstrap.ui).to receive(:info)
173
+
174
+ expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
175
+ bootstrap.send(:wait_for_remote_response, 2)
176
+ end
177
+
178
+ it 'should keep retrying at 10s intervals if the timeout in minutes has not elapsed' do
179
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
180
+ call_result_sequence.push(0)
181
+ allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
182
+ allow(bootstrap).to receive(:print)
183
+ allow(bootstrap.ui).to receive(:info)
184
+
185
+ expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
186
+ bootstrap.send(:wait_for_remote_response, 2)
187
+ end
188
+
189
+ it 'should have a wait timeout of 2 minutes by default' do
190
+ allow(bootstrap).to receive(:run_command).and_raise(WinRM::WinRMHTTPTransportError.new('','500'))
191
+ allow(bootstrap).to receive(:create_bootstrap_bat_command).and_raise(SystemExit)
192
+ expect(bootstrap).to receive(:wait_for_remote_response).with(2)
193
+ allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
194
+
195
+ allow(bootstrap.ui).to receive(:info)
196
+ bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
197
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
198
+ end
199
+
200
+ it 'should not a wait for timeout on Errno::ECONNREFUSED' do
201
+ allow(bootstrap).to receive(:run_command).and_raise(Errno::ECONNREFUSED.new)
202
+ allow(bootstrap.ui).to receive(:info)
203
+ bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
204
+ expect(bootstrap.ui).to receive(:error).with("Connection refused connecting to localhost:5985.")
205
+
206
+ # wait_for_remote_response is protected method, So define singleton test method to call it.
207
+ bootstrap.define_singleton_method(:test_wait_for_remote_response){wait_for_remote_response(bootstrap.options[:auth_timeout][:default])}
208
+ expect { bootstrap.test_wait_for_remote_response }.to raise_error(Errno::ECONNREFUSED)
209
+ end
210
+
211
+ it 'should stop retrying if more than 2 minutes has elapsed' do
212
+ times = [ Time.new(2014, 4, 1, 22, 25), Time.new(2014, 4, 1, 22, 51), Time.new(2014, 4, 1, 22, 28) ]
213
+ allow(Time).to receive(:now).and_return(*times)
214
+ run_command_result = lambda {raise WinRM::WinRMHTTPTransportError, '401'}
215
+ allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
216
+ allow(bootstrap).to receive(:run_command).and_return(run_command_result)
217
+ allow(bootstrap).to receive(:print)
218
+ allow(bootstrap.ui).to receive(:info)
219
+ allow(bootstrap.ui).to receive(:error)
220
+ expect(bootstrap).to receive(:run_command).exactly(1).times
221
+ bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
222
+ expect { bootstrap.bootstrap }.to raise_error RuntimeError
223
+ end
224
+
225
+ context "when validation_key is not present" do
226
+ context "using chef 11", :chef_lt_12_only do
227
+ before do
228
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
229
+ end
230
+
231
+ it 'raises an exception if validation_key is not present in chef 11' do
232
+ expect(bootstrap.ui).to receive(:error)
233
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
234
+ end
235
+ end
236
+
237
+ context "using chef 12", :chef_gte_12_only do
238
+ before do
239
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
240
+ bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
241
+ Chef::Config[:knife] = {:chef_node_name => 'foo.example.com'}
242
+ end
243
+
244
+ it 'raises an exception if winrm_authentication_protocol is basic and transport is plaintext' do
245
+ Chef::Config[:knife] = {:winrm_authentication_protocol => 'basic', :winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com'}
246
+ expect(bootstrap.ui).to receive(:error)
247
+ expect { bootstrap.run }.to raise_error(SystemExit)
248
+ end
249
+
250
+ it 'raises an exception if chef_node_name is not present ' do
251
+ Chef::Config[:knife] = {:chef_node_name => nil}
252
+ expect(bootstrap.client_builder).not_to receive(:run)
253
+ expect(bootstrap.client_builder).not_to receive(:client_path)
254
+ expect(bootstrap.ui).to receive(:error)
255
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
256
+ end
257
+ end
258
+ end
259
+
260
+ context "when doing chef vault", :chef_gte_12_only do
261
+ let(:vault_handler) { double('vault_handler', :doing_chef_vault? => true) }
262
+ let(:node_name) { 'foo.example.com' }
263
+ before do
264
+ allow(bootstrap).to receive(:wait_for_remote_response)
265
+ allow(bootstrap).to receive(:create_bootstrap_bat_command)
266
+ allow(bootstrap).to receive(:run_command).and_return(0)
267
+ bootstrap.config[:chef_node_name] = node_name
268
+ bootstrap.chef_vault_handler = vault_handler
269
+ end
270
+
271
+ context "builder does not respond to client" do
272
+ before do
273
+ bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
274
+ end
275
+
276
+ it "passes a node search query to the handler" do
277
+ expect(vault_handler).to receive(:run).with(node_name: node_name)
278
+ bootstrap.bootstrap
279
+ end
280
+ end
281
+
282
+ context "builder responds to client" do
283
+ let(:client) { Chef::ApiClient.new }
284
+
285
+ before do
286
+ bootstrap.client_builder = double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil, :client => client)
287
+ end
288
+
289
+ it "passes a node search query to the handler" do
290
+ expect(vault_handler).to receive(:run).with(client)
291
+ bootstrap.bootstrap
292
+ end
293
+ end
294
+ end
295
+ end