knife-windows 1.5.0 → 1.6.0

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +26 -26
  3. data/CHANGELOG.md +131 -121
  4. data/DOC_CHANGES.md +22 -14
  5. data/Gemfile +14 -13
  6. data/README.md +400 -392
  7. data/RELEASE_NOTES.md +2 -26
  8. data/appveyor.yml +39 -39
  9. data/ci.gemfile +16 -16
  10. data/knife-windows.gemspec +25 -25
  11. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +246 -233
  12. data/lib/chef/knife/bootstrap_windows_base.rb +443 -454
  13. data/lib/chef/knife/bootstrap_windows_ssh.rb +116 -115
  14. data/lib/chef/knife/bootstrap_windows_winrm.rb +102 -95
  15. data/lib/chef/knife/core/windows_bootstrap_context.rb +378 -378
  16. data/lib/chef/knife/knife_windows_base.rb +33 -33
  17. data/lib/chef/knife/windows_cert_generate.rb +155 -155
  18. data/lib/chef/knife/windows_cert_install.rb +68 -68
  19. data/lib/chef/knife/windows_helper.rb +36 -36
  20. data/lib/chef/knife/windows_listener_create.rb +107 -107
  21. data/lib/chef/knife/winrm.rb +122 -122
  22. data/lib/chef/knife/winrm_base.rb +123 -117
  23. data/lib/chef/knife/winrm_knife_base.rb +306 -305
  24. data/lib/chef/knife/winrm_session.rb +97 -91
  25. data/lib/chef/knife/winrm_shared_options.rb +47 -47
  26. data/lib/chef/knife/wsman_endpoint.rb +44 -44
  27. data/lib/chef/knife/wsman_test.rb +118 -118
  28. data/lib/knife-windows/path_helper.rb +234 -234
  29. data/lib/knife-windows/version.rb +6 -6
  30. data/spec/assets/win_template_rendered_with_bootstrap_install_command.txt +223 -223
  31. data/spec/assets/win_template_rendered_with_bootstrap_install_command_on_12_5_client.txt +223 -223
  32. data/spec/assets/win_template_rendered_without_bootstrap_install_command.txt +335 -335
  33. data/spec/assets/win_template_rendered_without_bootstrap_install_command_on_12_5_client.txt +335 -335
  34. data/spec/dummy_winrm_connection.rb +21 -0
  35. data/spec/functional/bootstrap_download_spec.rb +236 -241
  36. data/spec/spec_helper.rb +94 -94
  37. data/spec/unit/knife/bootstrap_options_spec.rb +157 -155
  38. data/spec/unit/knife/bootstrap_template_spec.rb +98 -98
  39. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +423 -426
  40. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +177 -177
  41. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -90
  42. data/spec/unit/knife/windows_cert_install_spec.rb +51 -51
  43. data/spec/unit/knife/windows_listener_create_spec.rb +76 -76
  44. data/spec/unit/knife/winrm_session_spec.rb +71 -76
  45. data/spec/unit/knife/winrm_spec.rb +500 -508
  46. data/spec/unit/knife/wsman_test_spec.rb +209 -209
  47. metadata +16 -17
  48. data/spec/dummy_winrm_service.rb +0 -24
@@ -1,98 +1,98 @@
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
- subject(:knife) { described_class.new }
34
-
35
- before(:all) do
36
- @original_config = Chef::Config.hash_dup
37
- @original_knife_config = Chef::Config[:knife].dup
38
- end
39
-
40
- after(:all) do
41
- Chef::Config.configuration = @original_config
42
- Chef::Config[:knife] = @original_knife_config
43
- end
44
-
45
- before(:each) do
46
- Chef::Log.logger = Logger.new(StringIO.new)
47
- @knife = Chef::Knife::BootstrapWindowsWinrm.new
48
- # Merge default settings in.
49
- @knife.merge_configs
50
- @knife.config[:template_file] = template_file
51
- @stdout = StringIO.new
52
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
53
- @stderr = StringIO.new
54
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
55
- end
56
-
57
- describe "specifying no_proxy with various entries" do
58
- let(:options){ ["--bootstrap-proxy", "", "--bootstrap-no-proxy", setting] }
59
-
60
- context "via --bootstrap-no-proxy" do
61
- let(:setting) { "api.chef.io" }
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.chef.io\".*})
65
- end
66
- end
67
- context "via --bootstrap-no-proxy multiple" do
68
- let(:setting) { "api.chef.io,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.chef.io,172.16.10.\*".*})
72
- end
73
- end
74
- end
75
-
76
- describe "specifying --msi-url" do
77
- context "with explicitly provided --msi-url" do
78
- let(:options) { ["--msi-url", "file:///something.msi"] }
79
-
80
- it "bootstrap batch file must fetch from provided url" do
81
- expect(rendered_template).to match(%r{.*REMOTE_SOURCE_MSI_URL=file:///something\.msi.*})
82
- end
83
- end
84
- context "with no provided --msi-url" do
85
- it "bootstrap batch file must fetch from provided url" do
86
- expect(rendered_template).to match(%r{.*REMOTE_SOURCE_MSI_URL=https://www\.chef\.io/.*})
87
- end
88
- end
89
- end
90
-
91
- describe "specifying knife_config[:architecture]" do
92
- it "puts the target architecture into the msi_url" do
93
- Chef::Config[:knife][:architecture] = :x86_64
94
- expect(rendered_template).to match(/MACHINE_ARCH=x86_64/)
95
- end
96
- end
97
-
98
- 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
+ subject(:knife) { described_class.new }
34
+
35
+ before(:all) do
36
+ @original_config = Chef::Config.hash_dup
37
+ @original_knife_config = Chef::Config[:knife].dup
38
+ end
39
+
40
+ after(:all) do
41
+ Chef::Config.configuration = @original_config
42
+ Chef::Config[:knife] = @original_knife_config
43
+ end
44
+
45
+ before(:each) do
46
+ Chef::Log.logger = Logger.new(StringIO.new)
47
+ @knife = Chef::Knife::BootstrapWindowsWinrm.new
48
+ # Merge default settings in.
49
+ @knife.merge_configs
50
+ @knife.config[:template_file] = template_file
51
+ @stdout = StringIO.new
52
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
53
+ @stderr = StringIO.new
54
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
55
+ end
56
+
57
+ describe "specifying no_proxy with various entries" do
58
+ let(:options){ ["--bootstrap-proxy", "", "--bootstrap-no-proxy", setting] }
59
+
60
+ context "via --bootstrap-no-proxy" do
61
+ let(:setting) { "api.chef.io" }
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.chef.io\".*})
65
+ end
66
+ end
67
+ context "via --bootstrap-no-proxy multiple" do
68
+ let(:setting) { "api.chef.io,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.chef.io,172.16.10.\*".*})
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "specifying --msi-url" do
77
+ context "with explicitly provided --msi-url" do
78
+ let(:options) { ["--msi-url", "file:///something.msi"] }
79
+
80
+ it "bootstrap batch file must fetch from provided url" do
81
+ expect(rendered_template).to match(%r{.*REMOTE_SOURCE_MSI_URL=file:///something\.msi.*})
82
+ end
83
+ end
84
+ context "with no provided --msi-url" do
85
+ it "bootstrap batch file must fetch from provided url" do
86
+ expect(rendered_template).to match(%r{.*REMOTE_SOURCE_MSI_URL=https://www\.chef\.io/.*})
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "specifying knife_config[:architecture]" do
92
+ it "puts the target architecture into the msi_url" do
93
+ Chef::Config[:knife][:architecture] = :x86_64
94
+ expect(rendered_template).to match(/MACHINE_ARCH=x86_64/)
95
+ end
96
+ end
97
+
98
+ end
@@ -1,426 +1,423 @@
1
- #
2
- # Author:: Adam Edwards(<adamed@chef.io>)
3
- # Copyright:: Copyright (c) 2014-2016 Chef Software, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'spec_helper'
20
- require 'winrm/output'
21
-
22
- Chef::Knife::Winrm.load_deps
23
-
24
- describe Chef::Knife::BootstrapWindowsWinrm do
25
- before do
26
- Chef::Config.reset
27
- bootstrap.config[:run_list] = []
28
- allow(bootstrap).to receive(:validate_options!).and_return(nil)
29
- allow(bootstrap).to receive(:sleep).and_return(10)
30
- allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session)
31
- allow(File).to receive(:exist?).with(anything).and_call_original
32
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
33
- end
34
-
35
- after do
36
- allow(bootstrap).to receive(:sleep).and_return(10)
37
- end
38
-
39
- let(:session_opts) do
40
- {
41
- user: "Administrator",
42
- password: "testpassword",
43
- port: "5986",
44
- transport: :ssl,
45
- host: "localhost"
46
- }
47
- end
48
- let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['winrm', '-d', 'windows-chef-client-msi', '-x', session_opts[:user], '-P', session_opts[:password], session_opts[:host]]) }
49
- let(:session) { Chef::Knife::WinrmSession.new(session_opts) }
50
- let(:arch_session_result) {
51
- o = WinRM::Output.new
52
- o[:data] << {stdout: "X86\r\n"}
53
- o
54
- }
55
- let(:arch_session_results) { [arch_session_result] }
56
- let(:initial_fail_count) { 4 }
57
-
58
- context "knife secret-file && knife secret options are passed" do
59
- before do
60
- Chef::Config.reset
61
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
62
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
63
- end
64
- it "gives preference to secret key passed under knife's secret-file option" do
65
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
66
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
67
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
68
- expect(bootstrap.load_correct_secret).to eq(
69
- "data_bag_secret_key_passed_under_knife_secret_file_option")
70
- end
71
- end
72
-
73
- context "cli secret-file && cli secret options are passed" do
74
- before do
75
- Chef::Config.reset
76
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
77
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
78
- end
79
- it "gives preference to secret key passed under cli's secret-file option" do
80
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
81
- bootstrap.config[:encrypted_data_bag_secret_file]).
82
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
83
- expect(bootstrap.load_correct_secret).to eq(
84
- "data_bag_secret_key_passed_under_cli_secret_file_option")
85
- end
86
- end
87
-
88
- context "knife secret-file, knife secret, cli secret-file && cli secret options are passed" do
89
- before do
90
- Chef::Config.reset
91
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/knife_encrypted_data_bag_secret"
92
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
93
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/cli_encrypted_data_bag_secret"
94
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
95
- end
96
- it "gives preference to secret key passed under cli's secret-file option" do
97
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
98
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
99
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
100
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
101
- bootstrap.config[:encrypted_data_bag_secret_file]).
102
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
103
- expect(bootstrap.load_correct_secret).to eq(
104
- "data_bag_secret_key_passed_under_cli_secret_file_option")
105
- end
106
- end
107
-
108
- context "knife secret-file && cli secret options are passed" do
109
- before do
110
- Chef::Config.reset
111
- Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
112
- bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
113
- end
114
- it "gives preference to secret key passed under cli's secret option" do
115
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
116
- Chef::Config[:knife][:encrypted_data_bag_secret_file]).
117
- and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
118
- expect(bootstrap.load_correct_secret).to eq(
119
- "data_bag_secret_key_passed_under_cli_secret_option")
120
- end
121
- end
122
-
123
- context "knife secret && cli secret-file options are passed" do
124
- before do
125
- Chef::Config.reset
126
- Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
127
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
128
- end
129
- it "gives preference to secret key passed under cli's secret-file option" do
130
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
131
- bootstrap.config[:encrypted_data_bag_secret_file]).
132
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
133
- expect(bootstrap.load_correct_secret).to eq(
134
- "data_bag_secret_key_passed_under_cli_secret_file_option")
135
- end
136
- end
137
-
138
- context "cli secret-file option is passed" do
139
- before do
140
- Chef::Config.reset
141
- bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
142
- end
143
- it "takes the secret key passed under cli's secret-file option" do
144
- allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
145
- bootstrap.config[:encrypted_data_bag_secret_file]).
146
- and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
147
- expect(bootstrap.load_correct_secret).to eq(
148
- "data_bag_secret_key_passed_under_cli_secret_file_option")
149
- end
150
- end
151
-
152
- it 'should raise an error if the relay_command returns an unknown response' do
153
- allow(session).to receive(:exit_code).and_return(500)
154
- allow(bootstrap).to receive(:wait_for_remote_response)
155
- allow(session).to receive(:relay_command).and_return(WinRM::Output.new)
156
- allow(bootstrap.ui).to receive(:info)
157
- expect {
158
- bootstrap.run
159
- }.to raise_error(/Response to 'echo %PROCESSOR_ARCHITECTURE%' command was invalid:/)
160
- end
161
-
162
- it 'should pass exit code from failed winrm call' do
163
- allow(session).to receive(:exit_code).and_return(500)
164
- allow(bootstrap).to receive(:wait_for_remote_response)
165
- allow(bootstrap).to receive(:create_bootstrap_bat_command)
166
- allow(session).to receive(:relay_command).and_return(arch_session_result)
167
- allow(bootstrap.ui).to receive(:info)
168
- expect {
169
- bootstrap.run_with_pretty_exceptions
170
- }.to raise_error(SystemExit) { |e|
171
- expect(e.status).to eq(500)
172
- }
173
- end
174
-
175
- it 'should retry if a 401 is received from WinRM' do
176
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '401')}}
177
- call_result_sequence.push(0)
178
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
179
- allow(bootstrap).to receive(:print)
180
- allow(bootstrap.ui).to receive(:info)
181
-
182
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
183
- bootstrap.send(:wait_for_remote_response, 2)
184
- end
185
-
186
- it 'should retry if something other than a 401 is received from WinRM' do
187
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
188
- call_result_sequence.push(0)
189
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
190
- allow(bootstrap).to receive(:print)
191
- allow(bootstrap.ui).to receive(:info)
192
-
193
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
194
- bootstrap.send(:wait_for_remote_response, 2)
195
- end
196
-
197
- it 'should keep retrying at 10s intervals if the timeout in minutes has not elapsed' do
198
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
199
- call_result_sequence.push(0)
200
- allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
201
- allow(bootstrap).to receive(:print)
202
- allow(bootstrap.ui).to receive(:info)
203
-
204
- expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
205
- bootstrap.send(:wait_for_remote_response, 2)
206
- end
207
-
208
- it 'should have a wait timeout of 2 minutes by default' do
209
- expect(bootstrap).to receive(:relay_winrm_command).with("echo %PROCESSOR_ARCHITECTURE%").and_return(arch_session_results)
210
- allow(bootstrap).to receive(:run_command).and_raise(WinRM::WinRMHTTPTransportError.new('','500'))
211
- allow(bootstrap).to receive(:create_bootstrap_bat_command).and_raise(SystemExit)
212
- expect(bootstrap).to receive(:wait_for_remote_response).with(2)
213
- allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
214
-
215
- allow(bootstrap.ui).to receive(:info)
216
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
217
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
218
- end
219
-
220
- it 'should not a wait for timeout on Errno::ECONNREFUSED' do
221
- allow(bootstrap).to receive(:run_command).and_raise(Errno::ECONNREFUSED.new)
222
- allow(bootstrap.ui).to receive(:info)
223
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
224
- expect(bootstrap.ui).to receive(:error).with("Connection refused connecting to localhost:5985.")
225
-
226
- # wait_for_remote_response is protected method, So define singleton test method to call it.
227
- bootstrap.define_singleton_method(:test_wait_for_remote_response){wait_for_remote_response(bootstrap.options[:auth_timeout][:default])}
228
- expect { bootstrap.test_wait_for_remote_response }.to raise_error(Errno::ECONNREFUSED)
229
- end
230
-
231
- it 'should stop retrying if more than 2 minutes has elapsed' do
232
- times = [ Time.new(2014, 4, 1, 22, 25), Time.new(2014, 4, 1, 22, 51), Time.new(2014, 4, 1, 22, 28) ]
233
- allow(Time).to receive(:now).and_return(*times)
234
- run_command_result = lambda {raise WinRM::WinRMHTTPTransportError, '401'}
235
- allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
236
- allow(bootstrap).to receive(:run_command).and_return(run_command_result)
237
- allow(bootstrap).to receive(:print)
238
- allow(bootstrap.ui).to receive(:info)
239
- allow(bootstrap.ui).to receive(:error)
240
- expect(bootstrap).to receive(:run_command).exactly(1).times
241
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
242
- expect { bootstrap.bootstrap }.to raise_error /Command execution failed./
243
- end
244
-
245
- it 'successfully bootstraps' do
246
- allow(bootstrap).to receive(:wait_for_remote_response)
247
- expect(bootstrap).to receive(:relay_winrm_command).with("echo %PROCESSOR_ARCHITECTURE%").and_return(arch_session_results)
248
- allow(bootstrap).to receive(:create_bootstrap_bat_command)
249
- allow(bootstrap).to receive(:run_command).and_return(0)
250
- expect(bootstrap.bootstrap).to eq(0)
251
- expect(Chef::Config[:knife][:architecture]).to eq(:i686)
252
- end
253
-
254
- context "when the target node is 64 bit" do
255
- let(:arch_session_result) {
256
- o = WinRM::Output.new
257
- o[:data] << {stdout: "AMD64\r\n"}
258
- o
259
- }
260
-
261
- it 'successfully bootstraps' do
262
- allow(bootstrap).to receive(:wait_for_remote_response)
263
- expect(bootstrap).to receive(:relay_winrm_command).with("echo %PROCESSOR_ARCHITECTURE%").and_return(arch_session_results)
264
- allow(bootstrap).to receive(:create_bootstrap_bat_command)
265
- allow(bootstrap).to receive(:run_command).and_return(0)
266
- expect(bootstrap.bootstrap).to eq(0)
267
- expect(Chef::Config[:knife][:architecture]).to eq(:x86_64)
268
- end
269
- end
270
-
271
- context "when validation_key is not present" do
272
- context "using chef 11", :chef_lt_12_only do
273
- before do
274
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
275
- end
276
-
277
- it 'raises an exception if validation_key is not present in chef 11' do
278
- expect(bootstrap.ui).to receive(:error)
279
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
280
- end
281
- end
282
-
283
- context "using chef 12", :chef_gte_12_only do
284
- before do
285
- allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
286
- bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
287
- Chef::Config[:knife] = {:chef_node_name => 'foo.example.com'}
288
- end
289
-
290
- it 'raises an exception if winrm_authentication_protocol is basic and transport is plaintext' do
291
- Chef::Config[:knife] = {:winrm_authentication_protocol => 'basic', :winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com'}
292
- expect(bootstrap.ui).to receive(:error)
293
- expect { bootstrap.run }.to raise_error(SystemExit)
294
- end
295
-
296
- it 'raises an exception if chef_node_name is not present ' do
297
- Chef::Config[:knife] = {:chef_node_name => nil}
298
- expect(bootstrap.client_builder).not_to receive(:run)
299
- expect(bootstrap.client_builder).not_to receive(:client_path)
300
- expect(bootstrap.ui).to receive(:error)
301
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
302
- end
303
- end
304
- end
305
-
306
- context "when doing chef vault", :chef_gte_12_only do
307
- let(:vault_handler) { double('vault_handler', :doing_chef_vault? => true) }
308
- let(:node_name) { 'foo.example.com' }
309
- before do
310
- allow(bootstrap).to receive(:wait_for_remote_response)
311
- expect(bootstrap).to receive(:relay_winrm_command).with("echo %PROCESSOR_ARCHITECTURE%").and_return(arch_session_results)
312
- allow(bootstrap).to receive(:create_bootstrap_bat_command)
313
- allow(bootstrap).to receive(:run_command).and_return(0)
314
- bootstrap.config[:chef_node_name] = node_name
315
- bootstrap.chef_vault_handler = vault_handler
316
- end
317
-
318
- context "builder does not respond to client" do
319
- before do
320
- bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
321
- end
322
-
323
- it "passes a node search query to the handler" do
324
- expect(vault_handler).to receive(:run).with(node_name: node_name)
325
- bootstrap.bootstrap
326
- end
327
- end
328
-
329
- context "builder responds to client" do
330
- let(:client) { Chef::ApiClient.new }
331
-
332
- before do
333
- bootstrap.client_builder = double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil, :client => client)
334
- end
335
-
336
- it "passes a node search query to the handler" do
337
- expect(vault_handler).to receive(:run).with(client)
338
- bootstrap.bootstrap
339
- end
340
- end
341
- end
342
-
343
- describe 'first_boot_attributes' do
344
- let(:first_boot_attributes) { { 'a1' => 'b1', 'a2' => 'b2', 'source' => 'hash' } }
345
- let(:json_file) { 'my_json.json' }
346
- let(:first_boot_attributes_from_file) { read_json_file(json_file) }
347
-
348
- before do
349
- File.open(json_file,"w+") do |f|
350
- f.write <<-EOH
351
- {"b2" : "a3", "a4" : "b5", "source" : "file"}
352
- EOH
353
- end
354
- end
355
-
356
- context 'when none of the json-attributes options are passed' do
357
- it 'returns an empty hash' do
358
- response = bootstrap.first_boot_attributes
359
- expect(response).to be == {}
360
- end
361
- end
362
-
363
- context 'when only --json-attributes option is passed' do
364
- before do
365
- bootstrap.config[:first_boot_attributes] = first_boot_attributes
366
- end
367
-
368
- it 'returns the hash passed by the user in --json-attributes option' do
369
- response = bootstrap.first_boot_attributes
370
- expect(response).to be == first_boot_attributes
371
- end
372
- end
373
-
374
- context 'when only --json-attribute-file option is passed' do
375
- before do
376
- bootstrap.config[:first_boot_attributes_from_file] = first_boot_attributes_from_file
377
- end
378
-
379
- it 'returns the hash passed by the user in --json-attribute-file option' do
380
- response = bootstrap.first_boot_attributes
381
- expect(response).to be == { 'b2' => 'a3', 'a4' => 'b5', 'source' => 'file' }
382
- end
383
- end
384
-
385
- context 'when both the --json-attributes option and --json-attribute-file options are passed' do
386
- before do
387
- bootstrap.config[:first_boot_attributes] = first_boot_attributes
388
- bootstrap.config[:first_boot_attributes_from_file] = first_boot_attributes_from_file
389
- end
390
-
391
- it 'returns the hash passed by the user in --json-attributes option' do
392
- response = bootstrap.first_boot_attributes
393
- expect(response).to be == first_boot_attributes
394
- end
395
- end
396
-
397
- after do
398
- FileUtils.rm_rf json_file
399
- end
400
- end
401
-
402
- describe 'render_template' do
403
- before do
404
- allow(bootstrap).to receive(:first_boot_attributes).and_return(
405
- { 'a1' => 'b3', 'a2' => 'b1' }
406
- )
407
- allow(bootstrap).to receive(:load_correct_secret).and_return(
408
- 'my_secret'
409
- )
410
- allow(Erubis::Eruby).to receive_message_chain(:new, :evaluate).and_return(
411
- 'my_template'
412
- )
413
- end
414
-
415
- it 'sets correct values into config and returns the correct response' do
416
- response = bootstrap.render_template
417
- expect(bootstrap.config[:first_boot_attributes]).to be == { 'a1' => 'b3', 'a2' => 'b1' }
418
- expect(bootstrap.config[:secret]).to be == 'my_secret'
419
- expect(response).to be == 'my_template'
420
- end
421
- end
422
- end
423
-
424
- def read_json_file(file)
425
- Chef::JSONCompat.parse(File.read(file))
426
- end
1
+ #
2
+ # Author:: Adam Edwards(<adamed@chef.io>)
3
+ # Copyright:: Copyright (c) 2014-2016 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'winrm/output'
21
+
22
+ Chef::Knife::Winrm.load_deps
23
+
24
+ describe Chef::Knife::BootstrapWindowsWinrm do
25
+ before do
26
+ Chef::Config.reset
27
+ bootstrap.config[:run_list] = []
28
+ allow(bootstrap).to receive(:validate_options!).and_return(nil)
29
+ allow(bootstrap).to receive(:sleep).and_return(10)
30
+ allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session)
31
+ allow(File).to receive(:exist?).with(anything).and_call_original
32
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
33
+ end
34
+
35
+ after do
36
+ allow(bootstrap).to receive(:sleep).and_return(10)
37
+ end
38
+
39
+ let(:session_opts) do
40
+ {
41
+ user: "Administrator",
42
+ password: "testpassword",
43
+ port: "5986",
44
+ transport: :ssl,
45
+ host: "localhost"
46
+ }
47
+ end
48
+ let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['winrm', '-d', 'windows-chef-client-msi', '-x', session_opts[:user], '-P', session_opts[:password], session_opts[:host]]) }
49
+ let(:session) { Chef::Knife::WinrmSession.new(session_opts) }
50
+ let(:arch_session_result) {
51
+ o = WinRM::Output.new
52
+ o << {stdout: "X86\r\n"}
53
+ o
54
+ }
55
+ let(:arch_session_results) { [arch_session_result] }
56
+ let(:initial_fail_count) { 4 }
57
+
58
+ context "knife secret-file && knife secret options are passed" do
59
+ before do
60
+ Chef::Config.reset
61
+ Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
62
+ Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
63
+ end
64
+ it "gives preference to secret key passed under knife's secret-file option" do
65
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
66
+ Chef::Config[:knife][:encrypted_data_bag_secret_file]).
67
+ and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
68
+ expect(bootstrap.load_correct_secret).to eq(
69
+ "data_bag_secret_key_passed_under_knife_secret_file_option")
70
+ end
71
+ end
72
+
73
+ context "cli secret-file && cli secret options are passed" do
74
+ before do
75
+ Chef::Config.reset
76
+ bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
77
+ bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
78
+ end
79
+ it "gives preference to secret key passed under cli's secret-file option" do
80
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
81
+ bootstrap.config[:encrypted_data_bag_secret_file]).
82
+ and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
83
+ expect(bootstrap.load_correct_secret).to eq(
84
+ "data_bag_secret_key_passed_under_cli_secret_file_option")
85
+ end
86
+ end
87
+
88
+ context "knife secret-file, knife secret, cli secret-file && cli secret options are passed" do
89
+ before do
90
+ Chef::Config.reset
91
+ Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/knife_encrypted_data_bag_secret"
92
+ Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
93
+ bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/cli_encrypted_data_bag_secret"
94
+ bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
95
+ end
96
+ it "gives preference to secret key passed under cli's secret-file option" do
97
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
98
+ Chef::Config[:knife][:encrypted_data_bag_secret_file]).
99
+ and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
100
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
101
+ bootstrap.config[:encrypted_data_bag_secret_file]).
102
+ and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
103
+ expect(bootstrap.load_correct_secret).to eq(
104
+ "data_bag_secret_key_passed_under_cli_secret_file_option")
105
+ end
106
+ end
107
+
108
+ context "knife secret-file && cli secret options are passed" do
109
+ before do
110
+ Chef::Config.reset
111
+ Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
112
+ bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
113
+ end
114
+ it "gives preference to secret key passed under cli's secret option" do
115
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
116
+ Chef::Config[:knife][:encrypted_data_bag_secret_file]).
117
+ and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
118
+ expect(bootstrap.load_correct_secret).to eq(
119
+ "data_bag_secret_key_passed_under_cli_secret_option")
120
+ end
121
+ end
122
+
123
+ context "knife secret && cli secret-file options are passed" do
124
+ before do
125
+ Chef::Config.reset
126
+ Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
127
+ bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
128
+ end
129
+ it "gives preference to secret key passed under cli's secret-file option" do
130
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
131
+ bootstrap.config[:encrypted_data_bag_secret_file]).
132
+ and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
133
+ expect(bootstrap.load_correct_secret).to eq(
134
+ "data_bag_secret_key_passed_under_cli_secret_file_option")
135
+ end
136
+ end
137
+
138
+ context "cli secret-file option is passed" do
139
+ before do
140
+ Chef::Config.reset
141
+ bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
142
+ end
143
+ it "takes the secret key passed under cli's secret-file option" do
144
+ allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
145
+ bootstrap.config[:encrypted_data_bag_secret_file]).
146
+ and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
147
+ expect(bootstrap.load_correct_secret).to eq(
148
+ "data_bag_secret_key_passed_under_cli_secret_file_option")
149
+ end
150
+ end
151
+
152
+ it 'should pass exit code from failed winrm call' do
153
+ allow(session).to receive(:exit_code).and_return(500)
154
+ allow(bootstrap).to receive(:wait_for_remote_response)
155
+ allow(bootstrap).to receive(:create_bootstrap_bat_command)
156
+ allow(session).to receive(:relay_command).and_return(arch_session_result)
157
+ allow(bootstrap.ui).to receive(:info)
158
+ expect {
159
+ bootstrap.run_with_pretty_exceptions
160
+ }.to raise_error(SystemExit) { |e|
161
+ expect(e.status).to eq(500)
162
+ }
163
+ end
164
+
165
+ it 'should retry if a 401 is received from WinRM' do
166
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '401')}}
167
+ call_result_sequence.push(0)
168
+ allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
169
+ allow(bootstrap).to receive(:print)
170
+ allow(bootstrap.ui).to receive(:info)
171
+
172
+ expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
173
+ bootstrap.send(:wait_for_remote_response, 2)
174
+ end
175
+
176
+ it 'should retry if something other than a 401 is received from WinRM' do
177
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
178
+ call_result_sequence.push(0)
179
+ allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
180
+ allow(bootstrap).to receive(:print)
181
+ allow(bootstrap.ui).to receive(:info)
182
+
183
+ expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
184
+ bootstrap.send(:wait_for_remote_response, 2)
185
+ end
186
+
187
+ it 'should keep retrying at 10s intervals if the timeout in minutes has not elapsed' do
188
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
189
+ call_result_sequence.push(0)
190
+ allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
191
+ allow(bootstrap).to receive(:print)
192
+ allow(bootstrap.ui).to receive(:info)
193
+
194
+ expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
195
+ bootstrap.send(:wait_for_remote_response, 2)
196
+ end
197
+
198
+ it 'should have a wait timeout of 2 minutes by default' do
199
+ allow(bootstrap).to receive(:run_command).and_raise(WinRM::WinRMHTTPTransportError.new('','500'))
200
+ allow(bootstrap).to receive(:create_bootstrap_bat_command).and_raise(SystemExit)
201
+ expect(bootstrap).to receive(:wait_for_remote_response).with(2)
202
+
203
+ allow(bootstrap.ui).to receive(:info)
204
+ bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
205
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
206
+ end
207
+
208
+ it 'should not a wait for timeout on Errno::ECONNREFUSED' do
209
+ allow(bootstrap).to receive(:run_command).and_raise(Errno::ECONNREFUSED.new)
210
+ allow(bootstrap.ui).to receive(:info)
211
+ bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
212
+ expect(bootstrap.ui).to receive(:error).with("Connection refused connecting to localhost:5985.")
213
+
214
+ # wait_for_remote_response is protected method, So define singleton test method to call it.
215
+ bootstrap.define_singleton_method(:test_wait_for_remote_response){wait_for_remote_response(bootstrap.options[:auth_timeout][:default])}
216
+ expect { bootstrap.test_wait_for_remote_response }.to raise_error(Errno::ECONNREFUSED)
217
+ end
218
+
219
+ it 'should stop retrying if more than 2 minutes has elapsed' do
220
+ times = [ Time.new(2014, 4, 1, 22, 25), Time.new(2014, 4, 1, 22, 51), Time.new(2014, 4, 1, 22, 28) ]
221
+ allow(Time).to receive(:now).and_return(*times)
222
+ run_command_result = lambda {raise WinRM::WinRMHTTPTransportError, '401'}
223
+ allow(bootstrap).to receive(:run_command).and_return(run_command_result)
224
+ allow(bootstrap).to receive(:print)
225
+ allow(bootstrap.ui).to receive(:info)
226
+ allow(bootstrap.ui).to receive(:error)
227
+ expect(bootstrap).to receive(:run_command).exactly(1).times
228
+ bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
229
+ expect { bootstrap.bootstrap }.to raise_error /Command execution failed./
230
+ end
231
+
232
+ it 'successfully bootstraps' do
233
+ Chef::Config[:knife][:bootstrap_architecture] = :i386
234
+ allow(bootstrap).to receive(:wait_for_remote_response)
235
+ allow(bootstrap).to receive(:create_bootstrap_bat_command)
236
+ allow(bootstrap).to receive(:run_command).and_return(0)
237
+ expect(bootstrap.bootstrap).to eq(0)
238
+ expect(Chef::Config[:knife][:architecture]).to eq(:i686)
239
+ end
240
+
241
+ context "when the target node is 64 bit" do
242
+ it 'successfully bootstraps' do
243
+ Chef::Config[:knife][:bootstrap_architecture] = :x86_64
244
+ allow(bootstrap).to receive(:wait_for_remote_response)
245
+ allow(bootstrap).to receive(:create_bootstrap_bat_command)
246
+ allow(bootstrap).to receive(:run_command).and_return(0)
247
+ expect(bootstrap.bootstrap).to eq(0)
248
+ expect(Chef::Config[:knife][:architecture]).to eq(:x86_64)
249
+ end
250
+ end
251
+
252
+ context 'FQDN validation -' do
253
+ it 'should raise an error if FQDN value is not passed' do
254
+ bootstrap.instance_variable_set(:@name_args, [])
255
+ allow(bootstrap.ui).to receive(:error)
256
+ expect {
257
+ bootstrap.run
258
+ }.to raise_error(SystemExit)
259
+ end
260
+
261
+ it 'should not raise error if FQDN value is passed' do
262
+ bootstrap.instance_variable_set(:@name_args, ["fqdn_name"])
263
+ expect {
264
+ bootstrap.run
265
+ }.not_to raise_error(SystemExit)
266
+ end
267
+ end
268
+
269
+ context "when validation_key is not present" do
270
+ context "using chef 11", :chef_lt_12_only do
271
+ before do
272
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
273
+ end
274
+
275
+ it 'raises an exception if validation_key is not present in chef 11' do
276
+ expect(bootstrap.ui).to receive(:error)
277
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
278
+ end
279
+ end
280
+
281
+ context "using chef 12", :chef_gte_12_only do
282
+ before do
283
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
284
+ bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
285
+ Chef::Config[:knife] = {:chef_node_name => 'foo.example.com'}
286
+ end
287
+
288
+ it 'raises an exception if winrm_authentication_protocol is basic and transport is plaintext' do
289
+ Chef::Config[:knife] = {:winrm_authentication_protocol => 'basic', :winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com'}
290
+ expect(bootstrap.ui).to receive(:error)
291
+ expect { bootstrap.run }.to raise_error(SystemExit)
292
+ end
293
+
294
+ it 'raises an exception if chef_node_name is not present ' do
295
+ Chef::Config[:knife] = {:chef_node_name => nil}
296
+ expect(bootstrap.client_builder).not_to receive(:run)
297
+ expect(bootstrap.client_builder).not_to receive(:client_path)
298
+ expect(bootstrap.ui).to receive(:error)
299
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
300
+ end
301
+ end
302
+ end
303
+
304
+ context "when doing chef vault", :chef_gte_12_only do
305
+ let(:vault_handler) { double('vault_handler', :doing_chef_vault? => true) }
306
+ let(:node_name) { 'foo.example.com' }
307
+ before do
308
+ allow(bootstrap).to receive(:wait_for_remote_response)
309
+ allow(bootstrap).to receive(:create_bootstrap_bat_command)
310
+ allow(bootstrap).to receive(:run_command).and_return(0)
311
+ bootstrap.config[:chef_node_name] = node_name
312
+ bootstrap.chef_vault_handler = vault_handler
313
+ end
314
+
315
+ context "builder does not respond to client" do
316
+ before do
317
+ bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
318
+ end
319
+
320
+ it "passes a node search query to the handler" do
321
+ expect(vault_handler).to receive(:run).with(node_name: node_name)
322
+ bootstrap.bootstrap
323
+ end
324
+ end
325
+
326
+ context "builder responds to client" do
327
+ let(:client) { Chef::ApiClient.new }
328
+
329
+ before do
330
+ bootstrap.client_builder = double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil, :client => client)
331
+ end
332
+
333
+ it "passes a node search query to the handler" do
334
+ expect(vault_handler).to receive(:run).with(client)
335
+ bootstrap.bootstrap
336
+ end
337
+ end
338
+ end
339
+
340
+ describe 'first_boot_attributes' do
341
+ let(:first_boot_attributes) { { 'a1' => 'b1', 'a2' => 'b2', 'source' => 'hash' } }
342
+ let(:json_file) { 'my_json.json' }
343
+ let(:first_boot_attributes_from_file) { read_json_file(json_file) }
344
+
345
+ before do
346
+ File.open(json_file,"w+") do |f|
347
+ f.write <<-EOH
348
+ {"b2" : "a3", "a4" : "b5", "source" : "file"}
349
+ EOH
350
+ end
351
+ end
352
+
353
+ context 'when none of the json-attributes options are passed' do
354
+ it 'returns an empty hash' do
355
+ response = bootstrap.first_boot_attributes
356
+ expect(response).to be == {}
357
+ end
358
+ end
359
+
360
+ context 'when only --json-attributes option is passed' do
361
+ before do
362
+ bootstrap.config[:first_boot_attributes] = first_boot_attributes
363
+ end
364
+
365
+ it 'returns the hash passed by the user in --json-attributes option' do
366
+ response = bootstrap.first_boot_attributes
367
+ expect(response).to be == first_boot_attributes
368
+ end
369
+ end
370
+
371
+ context 'when only --json-attribute-file option is passed' do
372
+ before do
373
+ bootstrap.config[:first_boot_attributes_from_file] = first_boot_attributes_from_file
374
+ end
375
+
376
+ it 'returns the hash passed by the user in --json-attribute-file option' do
377
+ response = bootstrap.first_boot_attributes
378
+ expect(response).to be == { 'b2' => 'a3', 'a4' => 'b5', 'source' => 'file' }
379
+ end
380
+ end
381
+
382
+ context 'when both the --json-attributes option and --json-attribute-file options are passed' do
383
+ before do
384
+ bootstrap.config[:first_boot_attributes] = first_boot_attributes
385
+ bootstrap.config[:first_boot_attributes_from_file] = first_boot_attributes_from_file
386
+ end
387
+
388
+ it 'returns the hash passed by the user in --json-attributes option' do
389
+ response = bootstrap.first_boot_attributes
390
+ expect(response).to be == first_boot_attributes
391
+ end
392
+ end
393
+
394
+ after do
395
+ FileUtils.rm_rf json_file
396
+ end
397
+ end
398
+
399
+ describe 'render_template' do
400
+ before do
401
+ allow(bootstrap).to receive(:first_boot_attributes).and_return(
402
+ { 'a1' => 'b3', 'a2' => 'b1' }
403
+ )
404
+ allow(bootstrap).to receive(:load_correct_secret).and_return(
405
+ 'my_secret'
406
+ )
407
+ allow(Erubis::Eruby).to receive_message_chain(:new, :evaluate).and_return(
408
+ 'my_template'
409
+ )
410
+ end
411
+
412
+ it 'sets correct values into config and returns the correct response' do
413
+ response = bootstrap.render_template
414
+ expect(bootstrap.config[:first_boot_attributes]).to be == { 'a1' => 'b3', 'a2' => 'b1' }
415
+ expect(bootstrap.config[:secret]).to be == 'my_secret'
416
+ expect(response).to be == 'my_template'
417
+ end
418
+ end
419
+ end
420
+
421
+ def read_json_file(file)
422
+ Chef::JSONCompat.parse(File.read(file))
423
+ end