knife-windows 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -5
  3. data/.travis.yml +26 -23
  4. data/CHANGELOG.md +108 -104
  5. data/DOC_CHANGES.md +14 -14
  6. data/Gemfile +12 -12
  7. data/LICENSE +201 -201
  8. data/README.md +385 -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 -25
  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 -366
  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 +117 -113
  28. data/lib/chef/knife/winrm_knife_base.rb +303 -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 -117
  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 +155 -154
  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 -177
  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 -201
  52. metadata +4 -4
@@ -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