knife-windows 1.1.0 → 1.1.1

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