knife-windows 1.1.0 → 1.1.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 +20 -20
  4. data/CHANGELOG.md +87 -83
  5. data/DOC_CHANGES.md +20 -20
  6. data/Gemfile +12 -12
  7. data/LICENSE +201 -201
  8. data/README.md +396 -396
  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 +28 -28
  16. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +247 -247
  17. data/lib/chef/knife/bootstrap_windows_base.rb +407 -401
  18. data/lib/chef/knife/bootstrap_windows_ssh.rb +110 -110
  19. data/lib/chef/knife/bootstrap_windows_winrm.rb +95 -102
  20. data/lib/chef/knife/core/windows_bootstrap_context.rb +362 -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 -212
  27. data/lib/chef/knife/winrm_base.rb +118 -118
  28. data/lib/chef/knife/winrm_knife_base.rb +309 -218
  29. data/lib/chef/knife/winrm_session.rb +82 -82
  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 +95 -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 -233
  41. data/spec/spec_helper.rb +88 -88
  42. data/spec/unit/knife/bootstrap_options_spec.rb +148 -146
  43. data/spec/unit/knife/bootstrap_template_spec.rb +92 -92
  44. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +259 -243
  45. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +151 -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 +73 -73
  50. data/spec/unit/knife/winrm_spec.rb +551 -504
  51. data/spec/unit/knife/wsman_test_spec.rb +178 -175
  52. metadata +3 -23
@@ -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,243 +1,259 @@
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
- # Kernel.stub(:sleep).and_return 10
32
- allow(bootstrap).to receive(:sleep).and_return(10)
33
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
34
- end
35
-
36
- after do
37
- # Kernel.unstub(:sleep)
38
- allow(bootstrap).to receive(:sleep).and_return(10)
39
- end
40
-
41
- let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['winrm', '-d', 'windows-chef-client-msi', '-x', 'Administrator', 'localhost']) }
42
- let(:session) { Chef::Knife::Winrm::WinrmSession.new({ :host => 'winrm.cloudapp.net', :port => '5986', :transport => :ssl }) }
43
-
44
- let(:initial_fail_count) { 4 }
45
-
46
- context "knife secret-file && knife secret options are passed" do
47
- before do
48
- Chef::Config.reset
49
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
50
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
51
- end
52
- it "gives preference to secret key passed under knife's secret-file option" do
53
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
54
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
55
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
56
- expect(bootstrap.load_correct_secret).to eq(
57
- "data_bag_secret_key_passed_under_knife_secret_file_option")
58
- end
59
- end
60
-
61
- context "cli secret-file && cli secret options are passed" do
62
- before do
63
- Chef::Config.reset
64
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
65
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
66
- end
67
- it "gives preference to secret key passed under cli's secret-file option" do
68
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
69
- bootstrap.config[:encrypted_data_bag_secret_file]).
70
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
71
- expect(bootstrap.load_correct_secret).to eq(
72
- "data_bag_secret_key_passed_under_cli_secret_file_option")
73
- end
74
- end
75
-
76
- context "knife secret-file, knife secret, cli secret-file && cli secret options are passed" do
77
- before do
78
- Chef::Config.reset
79
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/knife_encrypted_data_bag_secret"
80
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
81
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/cli_encrypted_data_bag_secret"
82
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
83
- end
84
- it "gives preference to secret key passed under cli's secret-file option" do
85
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
86
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
87
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
88
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
89
- bootstrap.config[:encrypted_data_bag_secret_file]).
90
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
91
- expect(bootstrap.load_correct_secret).to eq(
92
- "data_bag_secret_key_passed_under_cli_secret_file_option")
93
- end
94
- end
95
-
96
- context "knife secret-file && cli secret options are passed" do
97
- before do
98
- Chef::Config.reset
99
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
100
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
101
- end
102
- it "gives preference to secret key passed under cli's secret option" do
103
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
104
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
105
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
106
- expect(bootstrap.load_correct_secret).to eq(
107
- "data_bag_secret_key_passed_under_cli_secret_option")
108
- end
109
- end
110
-
111
- context "knife secret && cli secret-file options are passed" do
112
- before do
113
- Chef::Config.reset
114
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
115
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
116
- end
117
- it "gives preference to secret key passed under cli's secret-file option" do
118
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
119
- bootstrap.config[:encrypted_data_bag_secret_file]).
120
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
121
- expect(bootstrap.load_correct_secret).to eq(
122
- "data_bag_secret_key_passed_under_cli_secret_file_option")
123
- end
124
- end
125
-
126
- context "cli secret-file option is passed" do
127
- before do
128
- Chef::Config.reset
129
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
130
- end
131
- it "takes the secret key passed under cli's secret-file option" do
132
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
133
- bootstrap.config[:encrypted_data_bag_secret_file]).
134
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
135
- expect(bootstrap.load_correct_secret).to eq(
136
- "data_bag_secret_key_passed_under_cli_secret_file_option")
137
- end
138
- end
139
-
140
- it 'should retry if a 401 is received from WinRM' do
141
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '401')}}
142
- call_result_sequence.push(0)
143
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
144
- allow(bootstrap).to receive(:print)
145
- allow(bootstrap.ui).to receive(:info)
146
-
147
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
148
- bootstrap.send(:wait_for_remote_response, 2)
149
- end
150
-
151
- it 'should retry if something other than a 401 is received from WinRM' do
152
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
153
- call_result_sequence.push(0)
154
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
155
- allow(bootstrap).to receive(:print)
156
- allow(bootstrap.ui).to receive(:info)
157
-
158
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
159
- bootstrap.send(:wait_for_remote_response, 2)
160
- end
161
-
162
- it 'should keep retrying at 10s intervals if the timeout in minutes has not elapsed' do
163
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
164
- call_result_sequence.push(0)
165
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
166
- allow(bootstrap).to receive(:print)
167
- allow(bootstrap.ui).to receive(:info)
168
-
169
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
170
- bootstrap.send(:wait_for_remote_response, 2)
171
- end
172
-
173
- it 'should have a wait timeout of 2 minutes by default' do
174
- allow(bootstrap).to receive(:run_command).and_raise(WinRM::WinRMHTTPTransportError.new('','500'))
175
- allow(bootstrap).to receive(:create_bootstrap_bat_command).and_raise(SystemExit)
176
- expect(bootstrap).to receive(:wait_for_remote_response).with(2)
177
- allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
178
-
179
- allow(bootstrap.ui).to receive(:info)
180
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
181
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
182
- end
183
-
184
- it 'should not a wait for timeout on Errno::ECONNREFUSED' do
185
- allow(bootstrap).to receive(:run_command).and_raise(Errno::ECONNREFUSED.new)
186
- allow(bootstrap.ui).to receive(:info)
187
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
188
- expect(bootstrap.ui).to receive(:error).with("Connection refused connecting to localhost:5985.")
189
-
190
- # wait_for_remote_response is protected method, So define singleton test method to call it.
191
- bootstrap.define_singleton_method(:test_wait_for_remote_response){wait_for_remote_response(bootstrap.options[:auth_timeout][:default])}
192
- expect { bootstrap.test_wait_for_remote_response }.to raise_error(Errno::ECONNREFUSED)
193
- end
194
-
195
- it 'should stop retrying if more than 2 minutes has elapsed' do
196
- times = [ Time.new(2014, 4, 1, 22, 25), Time.new(2014, 4, 1, 22, 51), Time.new(2014, 4, 1, 22, 28) ]
197
- allow(Time).to receive(:now).and_return(*times)
198
- run_command_result = lambda {raise WinRM::WinRMHTTPTransportError, '401'}
199
- allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
200
- allow(bootstrap).to receive(:run_command).and_return(run_command_result)
201
- allow(bootstrap).to receive(:print)
202
- allow(bootstrap.ui).to receive(:info)
203
- allow(bootstrap.ui).to receive(:error)
204
- expect(bootstrap).to receive(:run_command).exactly(1).times
205
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
206
- expect { bootstrap.bootstrap }.to raise_error RuntimeError
207
- end
208
-
209
- context "when validation_key is not present" do
210
- context "using chef 11", :chef_lt_12_only do
211
- before do
212
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
213
- end
214
-
215
- it 'raises an exception if validation_key is not present in chef 11' do
216
- expect(bootstrap.ui).to receive(:error)
217
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
218
- end
219
- end
220
-
221
- context "using chef 12", :chef_gte_12_only do
222
- before do
223
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
224
- bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
225
- Chef::Config[:knife] = {:chef_node_name => 'foo.example.com'}
226
- end
227
-
228
- it 'raises an exception if winrm_authentication_protocol is basic and transport is plaintext' do
229
- Chef::Config[:knife] = {:winrm_authentication_protocol => 'basic', :winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com'}
230
- expect(bootstrap.ui).to receive(:error)
231
- expect { bootstrap.run }.to raise_error(SystemExit)
232
- end
233
-
234
- it 'raises an exception if chef_node_name is not present ' do
235
- Chef::Config[:knife] = {:chef_node_name => nil}
236
- expect(bootstrap.client_builder).not_to receive(:run)
237
- expect(bootstrap.client_builder).not_to receive(:client_path)
238
- expect(bootstrap.ui).to receive(:error)
239
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
240
- end
241
- end
242
- end
243
- 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
+ end