knife-windows 0.8.6 → 1.0.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +17 -3
  4. data/CHANGELOG.md +25 -6
  5. data/DOC_CHANGES.md +323 -0
  6. data/Gemfile +2 -1
  7. data/README.md +160 -29
  8. data/RELEASE_NOTES.md +59 -6
  9. data/appveyor.yml +42 -0
  10. data/ci.gemfile +15 -0
  11. data/knife-windows.gemspec +4 -2
  12. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +35 -21
  13. data/lib/chef/knife/bootstrap_windows_base.rb +155 -31
  14. data/lib/chef/knife/bootstrap_windows_ssh.rb +1 -1
  15. data/lib/chef/knife/bootstrap_windows_winrm.rb +17 -10
  16. data/lib/chef/knife/core/windows_bootstrap_context.rb +67 -16
  17. data/lib/chef/knife/windows_cert_generate.rb +155 -0
  18. data/lib/chef/knife/windows_cert_install.rb +62 -0
  19. data/lib/chef/knife/windows_helper.rb +3 -1
  20. data/lib/chef/knife/windows_listener_create.rb +100 -0
  21. data/lib/chef/knife/winrm.rb +84 -208
  22. data/lib/chef/knife/winrm_base.rb +36 -10
  23. data/lib/chef/knife/winrm_knife_base.rb +201 -0
  24. data/lib/chef/knife/winrm_session.rb +72 -0
  25. data/lib/chef/knife/winrm_shared_options.rb +47 -0
  26. data/lib/chef/knife/wsman_endpoint.rb +44 -0
  27. data/lib/chef/knife/wsman_test.rb +96 -0
  28. data/lib/knife-windows/path_helper.rb +77 -0
  29. data/lib/knife-windows/version.rb +1 -1
  30. data/spec/functional/bootstrap_download_spec.rb +41 -23
  31. data/spec/spec_helper.rb +11 -1
  32. data/spec/unit/knife/bootstrap_template_spec.rb +27 -27
  33. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +67 -23
  34. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +47 -0
  35. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -0
  36. data/spec/unit/knife/windows_cert_install_spec.rb +35 -0
  37. data/spec/unit/knife/windows_listener_create_spec.rb +61 -0
  38. data/spec/unit/knife/winrm_session_spec.rb +47 -0
  39. data/spec/unit/knife/winrm_spec.rb +222 -56
  40. data/spec/unit/knife/wsman_test_spec.rb +176 -0
  41. metadata +51 -20
@@ -0,0 +1,96 @@
1
+ #
2
+ # Author:: Steven Murawski (<smurawski@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 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 'httpclient'
20
+ require 'nokogiri'
21
+ require 'chef/knife'
22
+ require 'chef/knife/winrm_knife_base'
23
+ require 'chef/knife/wsman_endpoint'
24
+ require 'pry'
25
+
26
+ class Chef
27
+ class Knife
28
+ class WsmanTest < Knife
29
+
30
+ include Chef::Knife::WinrmCommandSharedFunctions
31
+
32
+ deps do
33
+ require 'chef/search/query'
34
+ end
35
+
36
+ banner "knife wsman test QUERY (options)"
37
+
38
+ def run
39
+ @config[:winrm_authentication_protocol] = 'basic'
40
+ configure_session
41
+ verify_wsman_accessiblity_for_nodes
42
+ end
43
+
44
+ def verify_wsman_accessiblity_for_nodes
45
+ error_count = 0
46
+ @winrm_sessions.each do |item|
47
+ Chef::Log.debug("checking for WSMAN availability at #{item.endpoint}")
48
+
49
+ xml = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd"><s:Header/><s:Body><wsmid:Identify/></s:Body></s:Envelope>'
50
+ header = {
51
+ 'WSMANIDENTIFY' => 'unauthenticated',
52
+ 'Content-Type' => 'application/soap+xml; charset=UTF-8'
53
+ }
54
+ output_object = Chef::Knife::WsmanEndpoint.new(item.host, item.port, item.endpoint)
55
+ error_message = nil
56
+ begin
57
+ client = HTTPClient.new
58
+ response = client.post(item.endpoint, xml, header)
59
+ rescue Exception => e
60
+ error_message = e.message
61
+ else
62
+ ui.msg "Connected successfully to #{item.host} at #{item.endpoint}."
63
+ output_object.response_status_code = response.status_code
64
+ end
65
+
66
+ if response.nil? || output_object.response_status_code != 200
67
+ error_message = "No valid WSMan endoint listening at #{item.endpoint}."
68
+ else
69
+ doc = Nokogiri::XML response.body
70
+ namespace = 'http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd'
71
+ output_object.protocol_version = doc.xpath('//wsmid:ProtocolVersion', 'wsmid' => namespace).text
72
+ output_object.product_version = doc.xpath('//wsmid:ProductVersion', 'wsmid' => namespace).text
73
+ output_object.product_vendor = doc.xpath('//wsmid:ProductVendor', 'wsmid' => namespace).text
74
+ if output_object.protocol_version.to_s.strip.length == 0
75
+ error_message = "Endpoint #{item.endpoint} on #{item.host} does not appear to be a WSMAN endpoint."
76
+ end
77
+ end
78
+
79
+ unless error_message.nil?
80
+ ui.warn "Failed to connect to #{item.host} at #{item.endpoint}."
81
+ output_object.error_message = error_message
82
+ error_count += 1
83
+ end
84
+
85
+ if config[:verbosity] >= 1
86
+ output(output_object)
87
+ end
88
+ end
89
+ if error_count > 0
90
+ ui.error "Failed to connect to #{error_count} nodes."
91
+ exit 1
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -148,6 +148,83 @@ module Knife
148
148
  def self.relative_path_from(from, to)
149
149
  pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from)))
150
150
  end
151
+
152
+ # Retrieves the "home directory" of the current user while trying to ascertain the existence
153
+ # of said directory. The path returned uses / for all separators (the ruby standard format).
154
+ # If the home directory doesn't exist or an error is otherwise encountered, nil is returned.
155
+ #
156
+ # If a set of path elements is provided, they are appended as-is to the home path if the
157
+ # homepath exists.
158
+ #
159
+ # If an optional block is provided, the joined path is passed to that block if the home path is
160
+ # valid and the result of the block is returned instead.
161
+ #
162
+ # Home-path discovery is performed once. If a path is discovered, that value is memoized so
163
+ # that subsequent calls to home_dir don't bounce around.
164
+ #
165
+ # See self.all_homes.
166
+ def self.home(*args)
167
+ @@home_dir ||= self.all_homes { |p| break p }
168
+ if @@home_dir
169
+ path = File.join(@@home_dir, *args)
170
+ block_given? ? (yield path) : path
171
+ end
172
+ end
173
+
174
+ # See self.home. This method performs a similar operation except that it yields all the different
175
+ # possible values of 'HOME' that one could have on this platform. Hence, on windows, if
176
+ # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice.
177
+ # This method goes out and checks the existence of each location at the time of the call.
178
+ #
179
+ # The return is a list of all the returned values from each block invocation or a list of paths
180
+ # if no block is provided.
181
+ def self.all_homes(*args)
182
+ paths = []
183
+ if Chef::Platform.windows?
184
+ # By default, Ruby uses the the following environment variables to determine Dir.home:
185
+ # HOME
186
+ # HOMEDRIVE HOMEPATH
187
+ # USERPROFILE
188
+ # Ruby only checks to see if the variable is specified - not if the directory actually exists.
189
+ # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive)
190
+ # while USERPROFILE points to the location where the user application settings and profile are stored. HOME
191
+ # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is
192
+ # HOMESHARE instead of HOMEDRIVE.
193
+ #
194
+ # We instead walk down the following and only include paths that actually exist.
195
+ # HOME
196
+ # HOMEDRIVE HOMEPATH
197
+ # HOMESHARE HOMEPATH
198
+ # USERPROFILE
199
+
200
+ paths << ENV['HOME']
201
+ paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
202
+ paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH']
203
+ paths << ENV['USERPROFILE']
204
+ end
205
+ paths << Dir.home if ENV['HOME']
206
+
207
+ # Depending on what environment variables we're using, the slashes can go in any which way.
208
+ # Just change them all to / to keep things consistent.
209
+ # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on
210
+ # the particular brand of kool-aid you consume. This code assumes that \ and / are both
211
+ # path separators on any system being used.
212
+ paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path }
213
+
214
+ # Filter out duplicate paths and paths that don't exist.
215
+ valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) }
216
+ valid_paths = valid_paths.uniq
217
+
218
+ # Join all optional path elements at the end.
219
+ # If a block is provided, invoke it - otherwise just return what we've got.
220
+ joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) }
221
+ if block_given?
222
+ joined_paths.each { |p| yield p }
223
+ else
224
+ joined_paths
225
+ end
226
+ end
227
+
151
228
  end
152
229
  end
153
230
  end
@@ -1,6 +1,6 @@
1
1
  module Knife
2
2
  module Windows
3
- VERSION = "0.8.6"
3
+ VERSION = "1.0.0.rc.0"
4
4
  MAJOR, MINOR, TINY = VERSION.split('.')
5
5
  end
6
6
  end
@@ -25,7 +25,7 @@ require 'tmpdir'
25
25
  # Windows nodes. The test modifies the Windows batch file generated
26
26
  # from an erb template in the plugin source in order to enable execution
27
27
  # of only the download functionality contained in the bootstrap template.
28
- # The test relies on knowledge of the fields of the template itself and
28
+ # The test relies on knowledge of the fields of the template itself and
29
29
  # also on knowledge of the contents and structure of the Windows batch
30
30
  # file generated by the template.
31
31
  #
@@ -34,7 +34,7 @@ require 'tmpdir'
34
34
  # occur, the bootstrap code should be refactored to explicitly expose
35
35
  # the download funcitonality separately from other tasks to make the
36
36
  # test more robust.
37
- describe 'Knife::Windows::Core msi download functionality for knife Windows winrm bootstrap template' do
37
+ describe 'Knife::Windows::Core msi download functionality for knife Windows winrm bootstrap template' do
38
38
 
39
39
  before(:all) do
40
40
  # Since we're always running 32-bit Ruby, fix the
@@ -43,7 +43,7 @@ describe 'Knife::Windows::Core msi download functionality for knife Windows winr
43
43
  if ENV["PROCESSOR_ARCHITEW6432"]
44
44
  ENV["PROCESSOR_ARCHITECTURE"] = ENV["PROCESSOR_ARCHITEW6432"]
45
45
  end
46
-
46
+
47
47
  # All file artifacts from this test will be written into this directory
48
48
  @temp_directory = Dir.mktmpdir("bootstrap_test")
49
49
 
@@ -51,8 +51,8 @@ describe 'Knife::Windows::Core msi download functionality for knife Windows winr
51
51
  # the downloaded msi
52
52
  @local_file_download_destination = "#{@temp_directory}/chef-client-latest.msi"
53
53
 
54
- source_code_directory = File.dirname(__FILE__)
55
- @template_file_path ="#{source_code_directory}/../../lib/chef/knife/bootstrap/windows-chef-client-msi.erb"
54
+ source_code_directory = File.dirname(__FILE__)
55
+ @template_file_path ="#{source_code_directory}/../../lib/chef/knife/bootstrap/windows-chef-client-msi.erb"
56
56
  end
57
57
 
58
58
  after(:all) do
@@ -71,7 +71,7 @@ describe 'Knife::Windows::Core msi download functionality for knife Windows winr
71
71
  # from being populated, i.e. chef installation and first chef
72
72
  # run sections
73
73
  allow(mock_bootstrap_context).to receive(:validation_key).and_return("echo.validation_key")
74
- allow(mock_bootstrap_context).to receive(:encrypted_data_bag_secret).and_return("echo.encrypted_data_bag_secret")
74
+ allow(mock_bootstrap_context).to receive(:secret).and_return("echo.encrypted_data_bag_secret")
75
75
  allow(mock_bootstrap_context).to receive(:config_content).and_return("echo.config_content")
76
76
  allow(mock_bootstrap_context).to receive(:start_chef).and_return("echo.echo start_chef_command")
77
77
  allow(mock_bootstrap_context).to receive(:run_list).and_return("echo.run_list")
@@ -86,25 +86,19 @@ describe 'Knife::Windows::Core msi download functionality for knife Windows winr
86
86
  allow(Chef::Knife::Winrm).to receive(:new).and_return(mock_winrm)
87
87
 
88
88
  allow(Chef::Knife::Core::WindowsBootstrapContext).to receive(:new).and_return(mock_bootstrap_context)
89
+ Chef::Config[:knife] = {:winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com', :winrm_authentication_protocol => 'negotiate'}
89
90
  end
90
91
 
91
- it "downloads the chef-client MSI during winrm bootstrap" do
92
-
93
- clean_test_case
94
-
95
- winrm_bootstrapper = Chef::Knife::BootstrapWindowsWinrm.new([ "127.0.0.1" ])
96
- allow(winrm_bootstrapper).to receive(:wait_for_remote_response)
97
- winrm_bootstrapper.config[:template_file] = @template_file_path
92
+ it "downloads the chef-client MSI from the default location during winrm bootstrap" do
93
+ run_download_scenario
94
+ end
98
95
 
99
- # Execute the commands locally that would normally be executed via WinRM
100
- allow(winrm_bootstrapper).to receive(:run_command) do |command|
101
- system(command)
96
+ context "when provided a custom msi_url to fetch from" do
97
+ let(:mock_bootstrap_context) { Chef::Knife::Core::WindowsBootstrapContext.new(
98
+ { :msi_url => "file:///C:/Windows/System32/xcopy.exe" }, nil, { :knife => {} }) }
99
+ it "downloads the chef-client MSI from a custom path during winrm bootstrap" do
100
+ run_download_scenario
102
101
  end
103
-
104
- winrm_bootstrapper.run
105
-
106
- # Download should succeed
107
- expect(download_succeeded?).to be true
108
102
  end
109
103
  end
110
104
 
@@ -118,5 +112,29 @@ describe 'Knife::Windows::Core msi download functionality for knife Windows winr
118
112
  File.delete(@local_file_download_destination)
119
113
  end
120
114
  end
121
-
122
- end
115
+
116
+ def run_download_scenario
117
+ clean_test_case
118
+
119
+ winrm_bootstrapper = Chef::Knife::BootstrapWindowsWinrm.new([ "127.0.0.1" ])
120
+
121
+ if chef_gte_12?
122
+ winrm_bootstrapper.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
123
+ elsif chef_lt_12?
124
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
125
+ end
126
+
127
+ allow(winrm_bootstrapper).to receive(:wait_for_remote_response)
128
+ winrm_bootstrapper.config[:template_file] = @template_file_path
129
+
130
+ # Execute the commands locally that would normally be executed via WinRM
131
+ allow(winrm_bootstrapper).to receive(:run_command) do |command|
132
+ system(command)
133
+ end
134
+
135
+ winrm_bootstrapper.run
136
+
137
+ # Download should succeed
138
+ expect(download_succeeded?).to be true
139
+ end
140
+ end
data/spec/spec_helper.rb CHANGED
@@ -23,6 +23,7 @@ end
23
23
 
24
24
  require_relative '../lib/chef/knife/core/windows_bootstrap_context'
25
25
  require_relative '../lib/chef/knife/bootstrap_windows_winrm'
26
+ require_relative '../lib/chef/knife/wsman_test'
26
27
 
27
28
  if windows?
28
29
  require 'ruby-wmi'
@@ -30,7 +31,7 @@ end
30
31
 
31
32
  def windows2012?
32
33
  is_win2k12 = false
33
-
34
+
34
35
  if windows?
35
36
  this_operating_system = WMI::Win32_OperatingSystem.find(:first)
36
37
  os_version = this_operating_system.send('Version')
@@ -53,9 +54,18 @@ def windows2012?
53
54
  is_win2k12
54
55
  end
55
56
 
57
+ def chef_gte_12?
58
+ Chef::VERSION.split('.').first.to_i >= 12
59
+ end
60
+
61
+ def chef_lt_12?
62
+ Chef::VERSION.split('.').first.to_i < 12
63
+ end
56
64
 
57
65
  RSpec.configure do |config|
58
66
  config.filter_run_excluding :windows_only => true unless windows?
59
67
  config.filter_run_excluding :windows_2012_only => true unless windows2012?
68
+ config.filter_run_excluding :chef_gte_12_only => true unless chef_gte_12?
69
+ config.filter_run_excluding :chef_lt_12_only => true unless chef_lt_12?
60
70
  end
61
71
 
@@ -20,25 +20,17 @@ TEMPLATE_FILE = File.expand_path(File.dirname(__FILE__)) + "/../../../lib/chef/k
20
20
 
21
21
  require 'spec_helper'
22
22
 
23
- describe "While Windows Bootstrapping" do
24
- context "the default Windows bootstrapping template" do
25
- bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
26
- bootstrap.config[:template_file] = TEMPLATE_FILE
27
-
28
- template = bootstrap.load_template
29
- template_file_lines = template.split('\n')
30
- it "should download Platform specific MSI" do
31
- download_url=template_file_lines.find {|l| l.include?("url=")}
32
- download_url.include?("%MACHINE_OS%") && download_url.include?("%MACHINE_ARCH%")
33
- end
34
- it "should download specific version of MSI if supplied" do
35
- download_url_ext= template_file_lines.find {|l| l.include?("url +=")}
36
- download_url_ext.include?("[:bootstrap_version]")
37
- end
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)
38
32
  end
39
- end
40
33
 
41
- describe Chef::Knife::BootstrapWindowsWinrm do
42
34
  before(:all) do
43
35
  @original_config = Chef::Config.hash_dup
44
36
  @original_knife_config = Chef::Config[:knife].dup
@@ -54,7 +46,7 @@ describe Chef::Knife::BootstrapWindowsWinrm do
54
46
  @knife = Chef::Knife::BootstrapWindowsWinrm.new
55
47
  # Merge default settings in.
56
48
  @knife.merge_configs
57
- @knife.config[:template_file] = TEMPLATE_FILE
49
+ @knife.config[:template_file] = template_file
58
50
  @stdout = StringIO.new
59
51
  allow(@knife.ui).to receive(:stdout).and_return(@stdout)
60
52
  @stderr = StringIO.new
@@ -64,15 +56,6 @@ describe Chef::Knife::BootstrapWindowsWinrm do
64
56
  describe "specifying no_proxy with various entries" do
65
57
  subject(:knife) { described_class.new }
66
58
  let(:options){ ["--bootstrap-proxy", "", "--bootstrap-no-proxy", setting] }
67
- let(:template_file) { TEMPLATE_FILE }
68
- let(:rendered_template) do
69
- knife.instance_variable_set("@template_file", template_file)
70
- knife.parse_options(options)
71
- # Avoid referencing a validation keyfile we won't find during #render_template
72
- template = IO.read(template_file).chomp
73
- template_string = template.gsub(/^.*[Vv]alidation_key.*$/, '')
74
- knife.render_template(template_string)
75
- end
76
59
 
77
60
  context "via --bootstrap-no-proxy" do
78
61
  let(:setting) { "api.opscode.com" }
@@ -89,4 +72,21 @@ describe Chef::Knife::BootstrapWindowsWinrm do
89
72
  end
90
73
  end
91
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
92
  end
@@ -18,6 +18,7 @@
18
18
 
19
19
  require 'spec_helper'
20
20
 
21
+ Chef::Knife::Winrm.load_deps
21
22
 
22
23
  describe Chef::Knife::BootstrapWindowsWinrm do
23
24
  before(:all) do
@@ -27,6 +28,7 @@ describe Chef::Knife::BootstrapWindowsWinrm do
27
28
  before do
28
29
  # Kernel.stub(:sleep).and_return 10
29
30
  allow(bootstrap).to receive(:sleep).and_return(10)
31
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
30
32
  end
31
33
 
32
34
  after do
@@ -34,73 +36,115 @@ describe Chef::Knife::BootstrapWindowsWinrm do
34
36
  allow(bootstrap).to receive(:sleep).and_return(10)
35
37
  end
36
38
 
37
- let (:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['winrm', '-d', 'windows-chef-client-msi', '-x', 'Administrator', 'localhost']) }
39
+ let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['winrm', '-d', 'windows-chef-client-msi', '-x', 'Administrator', 'localhost']) }
40
+ let(:session) { Chef::Knife::Winrm::WinrmSession.new({ :host => 'winrm.cloudapp.net', :port => '5986', :transport => :ssl }) }
41
+
42
+ let(:initial_fail_count) { 4 }
38
43
 
39
- let(:initial_fail_count) { 4 }
40
44
  it 'should retry if a 401 is received from WinRM' do
41
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('401', '401')}}
45
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '401')}}
42
46
  call_result_sequence.push(0)
43
47
  allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
48
+ allow(bootstrap).to receive(:print)
49
+ allow(bootstrap.ui).to receive(:info)
44
50
 
45
51
  expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
46
52
  bootstrap.send(:wait_for_remote_response, 2)
47
53
  end
48
54
 
49
55
  it 'should retry if something other than a 401 is received from WinRM' do
50
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('500', '500')}}
56
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
51
57
  call_result_sequence.push(0)
52
58
  allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
59
+ allow(bootstrap).to receive(:print)
60
+ allow(bootstrap.ui).to receive(:info)
53
61
 
54
62
  expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
55
63
  bootstrap.send(:wait_for_remote_response, 2)
56
64
  end
57
65
 
58
- it 'should have a wait timeout of 2 minutes by default' do
59
- allow(bootstrap).to receive(:run_command).and_raise(WinRM::WinRMHTTPTransportError.new('',''))
60
- allow(bootstrap).to receive(:create_bootstrap_bat_command).and_raise(SystemExit)
61
- expect(bootstrap).to receive(:wait_for_remote_response).with(2)
62
- allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
63
- bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
64
- expect { bootstrap.bootstrap }.to raise_error(SystemExit)
65
- end
66
-
67
66
  it 'should keep retrying at 10s intervals if the timeout in minutes has not elapsed' do
68
- call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('500', '500')}}
67
+ call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
69
68
  call_result_sequence.push(0)
70
69
  allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
70
+ allow(bootstrap).to receive(:print)
71
+ allow(bootstrap.ui).to receive(:info)
71
72
 
72
73
  expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
73
74
  bootstrap.send(:wait_for_remote_response, 2)
74
75
  end
75
76
 
77
+ it 'should have a wait timeout of 2 minutes by default' do
78
+ allow(bootstrap).to receive(:run_command).and_raise(WinRM::WinRMHTTPTransportError.new('','500'))
79
+ allow(bootstrap).to receive(:create_bootstrap_bat_command).and_raise(SystemExit)
80
+ expect(bootstrap).to receive(:wait_for_remote_response).with(2)
81
+ allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
82
+ allow(bootstrap.ui).to receive(:info)
83
+ bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
84
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
85
+ end
86
+
76
87
  it "should exit bootstrap with non-zero status if the bootstrap fails" do
77
88
  command_status = 1
78
89
 
79
90
  #Stub out calls to create the session and just get the exit codes back
80
91
  winrm_mock = Chef::Knife::Winrm.new
81
92
  allow(Chef::Knife::Winrm).to receive(:new).and_return(winrm_mock)
82
- allow(winrm_mock).to receive(:configure_session)
83
- allow(winrm_mock).to receive(:winrm_command)
84
- session_mock = EventMachine::WinRM::Session.new
85
- allow(EventMachine::WinRM::Session).to receive(:new).and_return(session_mock)
86
- allow(session_mock).to receive(:exit_codes).and_return({"thishost" => command_status})
87
-
93
+ allow(winrm_mock).to receive(:run).and_raise(SystemExit.new(command_status))
88
94
  #Skip over templating stuff and checking with the remote end
89
95
  allow(bootstrap).to receive(:create_bootstrap_bat_command)
90
96
  allow(bootstrap).to receive(:wait_for_remote_response)
91
-
97
+ allow(bootstrap.ui).to receive(:info)
98
+
92
99
  expect { bootstrap.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(command_status) }
93
100
  end
94
101
 
95
-
96
102
  it 'should stop retrying if more than 2 minutes has elapsed' do
97
103
  times = [ Time.new(2014, 4, 1, 22, 25), Time.new(2014, 4, 1, 22, 51), Time.new(2014, 4, 1, 22, 28) ]
98
104
  allow(Time).to receive(:now).and_return(*times)
99
- run_command_result = lambda {raise WinRM::WinRMHTTPTransportError.new('401', '401')}
105
+ run_command_result = lambda {raise WinRM::WinRMHTTPTransportError, '401'}
100
106
  allow(bootstrap).to receive(:validate_name_args!).and_return(nil)
101
107
  allow(bootstrap).to receive(:run_command).and_return(run_command_result)
108
+ allow(bootstrap).to receive(:print)
109
+ allow(bootstrap.ui).to receive(:info)
110
+ allow(bootstrap.ui).to receive(:error)
102
111
  expect(bootstrap).to receive(:run_command).exactly(1).times
103
112
  bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
104
113
  expect { bootstrap.bootstrap }.to raise_error RuntimeError
105
114
  end
115
+
116
+ context "when validation_key is not present" do
117
+ context "using chef 11", :chef_lt_12_only do
118
+ before do
119
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
120
+ end
121
+
122
+ it 'raises an exception if validation_key is not present in chef 11' do
123
+ expect(bootstrap.ui).to receive(:error)
124
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
125
+ end
126
+ end
127
+
128
+ context "using chef 12", :chef_gte_12_only do
129
+ before do
130
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
131
+ bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
132
+ Chef::Config[:knife] = {:chef_node_name => 'foo.example.com'}
133
+ end
134
+
135
+ it 'raises an exception if winrm_authentication_protocol is basic and transport is plaintext' do
136
+ Chef::Config[:knife] = {:winrm_authentication_protocol => 'basic', :winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com'}
137
+ expect(bootstrap.ui).to receive(:error)
138
+ expect { bootstrap.run }.to raise_error(SystemExit)
139
+ end
140
+
141
+ it 'raises an exception if chef_node_name is not present ' do
142
+ Chef::Config[:knife] = {:chef_node_name => nil}
143
+ expect(bootstrap.client_builder).not_to receive(:run)
144
+ expect(bootstrap.client_builder).not_to receive(:client_path)
145
+ expect(bootstrap.ui).to receive(:error)
146
+ expect { bootstrap.bootstrap }.to raise_error(SystemExit)
147
+ end
148
+ end
149
+ end
106
150
  end