knife-winops 2.0.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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +30 -0
  5. data/CHANGELOG.md +147 -0
  6. data/DOC_CHANGES.md +22 -0
  7. data/Gemfile +13 -0
  8. data/LICENSE +201 -0
  9. data/README.md +430 -0
  10. data/RELEASE_NOTES.md +17 -0
  11. data/Rakefile +21 -0
  12. data/appveyor.yml +36 -0
  13. data/ci.gemfile +15 -0
  14. data/knife-winops.gemspec +26 -0
  15. data/lib/chef/knife/bootstrap/Chef_bootstrap.erb +44 -0
  16. data/lib/chef/knife/bootstrap/bootstrap.ps1 +134 -0
  17. data/lib/chef/knife/bootstrap/tail.cmd +15 -0
  18. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +302 -0
  19. data/lib/chef/knife/bootstrap_windows_base.rb +473 -0
  20. data/lib/chef/knife/bootstrap_windows_ssh.rb +115 -0
  21. data/lib/chef/knife/bootstrap_windows_winrm.rb +102 -0
  22. data/lib/chef/knife/core/windows_bootstrap_context.rb +356 -0
  23. data/lib/chef/knife/knife_windows_base.rb +33 -0
  24. data/lib/chef/knife/windows_cert_generate.rb +155 -0
  25. data/lib/chef/knife/windows_cert_install.rb +68 -0
  26. data/lib/chef/knife/windows_helper.rb +36 -0
  27. data/lib/chef/knife/windows_listener_create.rb +107 -0
  28. data/lib/chef/knife/winrm.rb +127 -0
  29. data/lib/chef/knife/winrm_base.rb +128 -0
  30. data/lib/chef/knife/winrm_knife_base.rb +315 -0
  31. data/lib/chef/knife/winrm_session.rb +101 -0
  32. data/lib/chef/knife/winrm_shared_options.rb +54 -0
  33. data/lib/chef/knife/wsman_endpoint.rb +44 -0
  34. data/lib/chef/knife/wsman_test.rb +118 -0
  35. data/lib/knife-winops/path_helper.rb +242 -0
  36. data/lib/knife-winops/version.rb +6 -0
  37. data/spec/assets/fake_trusted_certs/excluded.txt +2 -0
  38. data/spec/assets/fake_trusted_certs/github.pem +42 -0
  39. data/spec/assets/fake_trusted_certs/google.crt +41 -0
  40. data/spec/assets/win_fake_trusted_cert_script.txt +89 -0
  41. data/spec/dummy_winrm_connection.rb +21 -0
  42. data/spec/functional/bootstrap_download_spec.rb +229 -0
  43. data/spec/spec_helper.rb +93 -0
  44. data/spec/unit/knife/bootstrap_options_spec.rb +164 -0
  45. data/spec/unit/knife/bootstrap_template_spec.rb +98 -0
  46. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +410 -0
  47. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +292 -0
  48. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -0
  49. data/spec/unit/knife/windows_cert_install_spec.rb +51 -0
  50. data/spec/unit/knife/windows_listener_create_spec.rb +76 -0
  51. data/spec/unit/knife/winrm_session_spec.rb +101 -0
  52. data/spec/unit/knife/winrm_spec.rb +494 -0
  53. data/spec/unit/knife/wsman_test_spec.rb +209 -0
  54. metadata +157 -0
@@ -0,0 +1,164 @@
1
+ #
2
+ # Original knife-windows author:: Kartik Null Cating-Subramanian(<ksubramanian@chef.io>)
3
+ # Copyright:: Copyright (c) 2015-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
+
21
+ describe Chef::Knife::Bootstrap, :chef_gte_12_7_only do
22
+ before(:all) do
23
+ Chef::Config.reset
24
+ end
25
+
26
+ let(:bootstrap) { Chef::Knife::Bootstrap.new }
27
+ let(:win_bootstrap) { nil }
28
+ let(:opt_map) { {} }
29
+ let(:ref_ignore) { [] }
30
+ let(:win_ignore) { [] }
31
+
32
+ def compare_property(sym)
33
+ win_bootstrap.options.each do |win_key, win_val|
34
+ unless win_ignore.include?(win_key) || opt_map.include?(win_key) then
35
+ actual = win_val[sym]
36
+ expected = bootstrap.options[win_key][sym]
37
+ expect(actual).to eq(expected),
38
+ "#{win_key} flag's #{sym} property doesn't match
39
+
40
+ expected: #{expected}
41
+ got: #{actual}"
42
+ end
43
+ end
44
+ end
45
+
46
+ shared_examples 'compare_options' do
47
+ it 'contains the option flags' do
48
+ opt_map.default_proc = proc { |map, key| key }
49
+ filtered_keys = (win_bootstrap.options.keys - win_ignore).map! { |key| opt_map[key] }
50
+
51
+ expect(filtered_keys).to match_array(bootstrap.options.keys - ref_ignore)
52
+ end
53
+
54
+ it 'uses the same long-name' do
55
+ compare_property(:long)
56
+ end
57
+
58
+ it 'uses the same short-name' do
59
+ compare_property(:short)
60
+ end
61
+
62
+ it 'uses the same description' do
63
+ compare_property(:description)
64
+ end
65
+
66
+ it 'uses the same default value' do
67
+ compare_property(:default)
68
+ end
69
+ end
70
+
71
+ context 'when compared to BootstrapWindowsWinrm' do
72
+ let(:win_bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new }
73
+
74
+ # opt_map: Hash of symbols in windows mapping to symbols in core. Name checks are
75
+ # ignored for these.
76
+ let(:opt_map) { {
77
+ :msi_url => :bootstrap_url,
78
+ :encrypted_data_bag_secret => :secret,
79
+ :encrypted_data_bag_secret_file => :secret_file,
80
+ :winrm_user => :ssh_user,
81
+ :winrm_password => :ssh_password,
82
+ :winrm_port => :ssh_port,
83
+ :winrm_ssl_verify_mode => :host_key_verify,
84
+ :bootstrap_install_command => :bootstrap_install_command,
85
+ :hint => :hint
86
+ }}
87
+
88
+ # ref_ignore: Options in core that are not implemented here.
89
+ let(:ref_ignore) { [
90
+ # These are irrelevant to WinRM.
91
+ :bootstrap_curl_options,
92
+ :bootstrap_wget_options,
93
+ :forward_agent,
94
+ :preserve_home,
95
+ :ssh_gateway,
96
+ :use_sudo,
97
+ :use_sudo_password,
98
+ :encrypt, # irrelevant during bootstrap
99
+ :identity_file,
100
+ :ssh_identity_file,
101
+ :ssh_gateway_identity,
102
+ :bootstrap_proxy_user,
103
+ :bootstrap_proxy_pass,
104
+ ]}
105
+
106
+ # win_ignore: Options in windows that aren't relevant to core.
107
+ let(:win_ignore) { [
108
+ :attribute,
109
+ :auth_timeout,
110
+ :ca_trust_file,
111
+ :install_as_service,
112
+ :kerberos_keytab_file,
113
+ :kerberos_realm,
114
+ :kerberos_service,
115
+ :manual,
116
+ :session_timeout,
117
+ :ssl_peer_fingerprint,
118
+ :winrm_authentication_protocol,
119
+ :winrm_transport,
120
+ :winrm_codepage,
121
+ :concurrency,
122
+ :winrm_shell,
123
+ :bootstrap_debug,
124
+ ] }
125
+
126
+ include_examples 'compare_options'
127
+ end
128
+
129
+ context 'when compared to BootstrapWindowsSsh' do
130
+ let(:win_bootstrap) { Chef::Knife::BootstrapWindowsSsh.new }
131
+
132
+ # opt_map: Hash of symbols in windows mapping to symbols in core. Name checks are
133
+ # ignored for these.
134
+ let(:opt_map) { {
135
+ :msi_url => :bootstrap_url,
136
+ :encrypted_data_bag_secret => :secret,
137
+ :encrypted_data_bag_secret_file => :secret_file,
138
+ :bootstrap_install_command => :bootstrap_install_command,
139
+ :hint => :hint
140
+ }}
141
+ # ref_ignore: Options in core that are not implemented here.
142
+ let(:ref_ignore) { [
143
+ :bootstrap_curl_options,
144
+ :bootstrap_wget_options,
145
+ :preserve_home,
146
+ :use_sudo,
147
+ :use_sudo_password,
148
+ :encrypt, # irrelevant during bootstrap
149
+ :ssh_gateway_identity,
150
+ :bootstrap_proxy_user,
151
+ :bootstrap_proxy_pass,
152
+ ]}
153
+ # win_ignore: Options in windows that aren't relevant to core.
154
+ let(:win_ignore) { [
155
+ :auth_timeout,
156
+ :install_as_service,
157
+ :host_key_verification, # Deprecated - remove this when the flag is removed.
158
+ :bootstrap_debug,
159
+ ] }
160
+
161
+ include_examples 'compare_options'
162
+ end
163
+
164
+ end
@@ -0,0 +1,98 @@
1
+ #
2
+ # Original knife-windows 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{.*CHEF_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{.*CHEF_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
@@ -0,0 +1,410 @@
1
+ #
2
+ # Original knife-windows 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
+ before do
271
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
272
+ bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
273
+ Chef::Config[:knife] = {:chef_node_name => 'foo.example.com'}
274
+ end
275
+
276
+ it 'raises an exception if winrm_authentication_protocol is basic and transport is plaintext' do
277
+ Chef::Config[:knife] = {:winrm_authentication_protocol => 'basic', :winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com'}
278
+ expect(bootstrap.ui).to receive(:error)
279
+ expect { bootstrap.run }.to raise_error(SystemExit)
280
+ end
281
+
282
+ it 'raises an exception if chef_node_name is not present ' do
283
+ Chef::Config[:knife] = {:chef_node_name => nil}
284
+ expect(bootstrap.client_builder).not_to receive(:run)
285
+ expect(bootstrap.client_builder).not_to receive(:client_path)
286
+ expect(bootstrap.ui).to receive(:error)
287
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
288
+ end
289
+ end
290
+
291
+ context "when doing chef vault" do
292
+ let(:vault_handler) { double('vault_handler', :doing_chef_vault? => true) }
293
+ let(:node_name) { 'foo.example.com' }
294
+ before do
295
+ allow(bootstrap).to receive(:wait_for_remote_response)
296
+ allow(bootstrap).to receive(:create_bootstrap_bat_command)
297
+ allow(bootstrap).to receive(:run_command).and_return(0)
298
+ bootstrap.config[:chef_node_name] = node_name
299
+ bootstrap.chef_vault_handler = vault_handler
300
+ end
301
+
302
+ context "builder does not respond to client" do
303
+ before do
304
+ bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
305
+ end
306
+
307
+ it "passes a node search query to the handler" do
308
+ expect(vault_handler).to receive(:run).with(node_name: node_name)
309
+ bootstrap.bootstrap
310
+ end
311
+ end
312
+
313
+ context "builder responds to client" do
314
+ let(:client) { Chef::ApiClient.new }
315
+
316
+ before do
317
+ bootstrap.client_builder = double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil, :client => client)
318
+ end
319
+
320
+ it "passes a node search query to the handler" do
321
+ expect(vault_handler).to receive(:run).with(client)
322
+ bootstrap.bootstrap
323
+ end
324
+ end
325
+ end
326
+
327
+ describe 'first_boot_attributes' do
328
+ let(:first_boot_attributes) { { 'a1' => 'b1', 'a2' => 'b2', 'source' => 'hash' } }
329
+ let(:json_file) { 'my_json.json' }
330
+ let(:first_boot_attributes_from_file) { read_json_file(json_file) }
331
+
332
+ before do
333
+ File.open(json_file,"w+") do |f|
334
+ f.write <<-EOH
335
+ {"b2" : "a3", "a4" : "b5", "source" : "file"}
336
+ EOH
337
+ end
338
+ end
339
+
340
+ context 'when none of the json-attributes options are passed' do
341
+ it 'returns an empty hash' do
342
+ response = bootstrap.first_boot_attributes
343
+ expect(response).to be == {}
344
+ end
345
+ end
346
+
347
+ context 'when only --json-attributes option is passed' do
348
+ before do
349
+ bootstrap.config[:first_boot_attributes] = first_boot_attributes
350
+ end
351
+
352
+ it 'returns the hash passed by the user in --json-attributes option' do
353
+ response = bootstrap.first_boot_attributes
354
+ expect(response).to be == first_boot_attributes
355
+ end
356
+ end
357
+
358
+ context 'when only --json-attribute-file option is passed' do
359
+ before do
360
+ bootstrap.config[:first_boot_attributes_from_file] = first_boot_attributes_from_file
361
+ end
362
+
363
+ it 'returns the hash passed by the user in --json-attribute-file option' do
364
+ response = bootstrap.first_boot_attributes
365
+ expect(response).to be == { 'b2' => 'a3', 'a4' => 'b5', 'source' => 'file' }
366
+ end
367
+ end
368
+
369
+ context 'when both the --json-attributes option and --json-attribute-file options are passed' do
370
+ before do
371
+ bootstrap.config[:first_boot_attributes] = first_boot_attributes
372
+ bootstrap.config[:first_boot_attributes_from_file] = first_boot_attributes_from_file
373
+ end
374
+
375
+ it 'returns the hash passed by the user in --json-attributes option' do
376
+ response = bootstrap.first_boot_attributes
377
+ expect(response).to be == first_boot_attributes
378
+ end
379
+ end
380
+
381
+ after do
382
+ FileUtils.rm_rf json_file
383
+ end
384
+ end
385
+
386
+ describe 'render_template' do
387
+ before do
388
+ allow(bootstrap).to receive(:first_boot_attributes).and_return(
389
+ { 'a1' => 'b3', 'a2' => 'b1' }
390
+ )
391
+ allow(bootstrap).to receive(:load_correct_secret).and_return(
392
+ 'my_secret'
393
+ )
394
+ allow(Erubis::Eruby).to receive_message_chain(:new, :evaluate).and_return(
395
+ 'my_template'
396
+ )
397
+ end
398
+
399
+ it 'sets correct values into config and returns the correct response' do
400
+ response = bootstrap.render_template
401
+ expect(bootstrap.config[:first_boot_attributes]).to be == { 'a1' => 'b3', 'a2' => 'b1' }
402
+ expect(bootstrap.config[:secret]).to be == 'my_secret'
403
+ expect(response).to be == 'my_template'
404
+ end
405
+ end
406
+ end
407
+
408
+ def read_json_file(file)
409
+ Chef::JSONCompat.parse(File.read(file))
410
+ end