knife-windows 1.9.6 → 3.0.3

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.
@@ -1,242 +0,0 @@
1
- #
2
- # Author:: Bryan McLellan <btm@loftninjas.org>
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
- # Sourced from Chef::Util::PathHelper.
20
- # Should be removed when Chef 11 catches up or we stop supporting Chef 11
21
-
22
- module Knife
23
- module Windows
24
- class PathHelper
25
- # Maximum characters in a standard Windows path (260 including drive letter and NUL)
26
- WIN_MAX_PATH = 259
27
-
28
- def self.dirname(path)
29
- if Chef::Platform.windows?
30
- # Find the first slash, not counting trailing slashes
31
- end_slash = path.size
32
- while true
33
- slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1)
34
- if !slash
35
- return end_slash == path.size ? '.' : path_separator
36
- elsif slash == end_slash - 1
37
- end_slash = slash
38
- else
39
- return path[0..slash-1]
40
- end
41
- end
42
- else
43
- ::File.dirname(path)
44
- end
45
- end
46
-
47
- BACKSLASH = '\\'.freeze
48
-
49
- def self.path_separator
50
- if Chef::Platform.windows?
51
- File::ALT_SEPARATOR || BACKSLASH
52
- else
53
- File::SEPARATOR
54
- end
55
- end
56
-
57
- def self.join(*args)
58
- args.flatten.inject do |joined_path, component|
59
- # Joined path ends with /
60
- joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '')
61
- component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '')
62
- joined_path += "#{path_separator}#{component}"
63
- end
64
- end
65
-
66
- def self.validate_path(path)
67
- if Chef::Platform.windows?
68
- unless printable?(path)
69
- msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings."
70
- Chef::Log.error(msg)
71
- raise Chef::Exceptions::ValidationFailed, msg
72
- end
73
-
74
- if windows_max_length_exceeded?(path)
75
- Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
76
- path.insert(0, "\\\\?\\")
77
- end
78
- end
79
-
80
- path
81
- end
82
-
83
- def self.windows_max_length_exceeded?(path)
84
- # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API
85
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
86
- unless path =~ /^\\\\?\\/
87
- if path.length > WIN_MAX_PATH
88
- return true
89
- end
90
- end
91
-
92
- false
93
- end
94
-
95
- def self.printable?(string)
96
- # returns true if string is free of non-printable characters (escape sequences)
97
- # this returns false for whitespace escape sequences as well, e.g. \n\t
98
- if string =~ /[^[:print:]]/
99
- false
100
- else
101
- true
102
- end
103
- end
104
-
105
- # Produces a comparable path.
106
- def self.canonical_path(path, add_prefix=true)
107
- # Rather than find an equivalent for File.absolute_path on 1.8.7, just bail out
108
- raise NotImplementedError, "This feature is not supported on Ruby versions < 1.9" if RUBY_VERSION.to_f < 1.9
109
-
110
- # First remove extra separators and resolve any relative paths
111
- abs_path = File.absolute_path(path)
112
-
113
- if Chef::Platform.windows?
114
- # Add the \\?\ API prefix on Windows unless add_prefix is false
115
- # Downcase on Windows where paths are still case-insensitive
116
- abs_path.gsub!(::File::SEPARATOR, path_separator)
117
- if add_prefix && abs_path !~ /^\\\\?\\/
118
- abs_path.insert(0, "\\\\?\\")
119
- end
120
-
121
- abs_path.downcase!
122
- end
123
-
124
- abs_path
125
- end
126
-
127
- def self.cleanpath(path)
128
- path = Pathname.new(path).cleanpath.to_s
129
- # ensure all forward slashes are backslashes
130
- if Chef::Platform.windows?
131
- path = path.gsub(File::SEPARATOR, path_separator)
132
- end
133
- path
134
- end
135
-
136
- def self.paths_eql?(path1, path2)
137
- canonical_path(path1) == canonical_path(path2)
138
- end
139
-
140
- # Note: this method is deprecated. Please use escape_glob_dirs
141
- # Paths which may contain glob-reserved characters need
142
- # to be escaped before globbing can be done.
143
- # http://stackoverflow.com/questions/14127343
144
- def self.escape_glob(*parts)
145
- path = cleanpath(join(*parts))
146
- path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x }
147
- end
148
-
149
- # This function does not switch to backslashes for windows
150
- # This is because only forwardslashes should be used with dir (even for windows)
151
- def self.escape_glob_dir(*parts)
152
- path = Pathname.new(join(*parts)).cleanpath.to_s
153
- path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\" + x }
154
- end
155
-
156
- def self.relative_path_from(from, to)
157
- pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from)))
158
- end
159
-
160
- # Retrieves the "home directory" of the current user while trying to ascertain the existence
161
- # of said directory. The path returned uses / for all separators (the ruby standard format).
162
- # If the home directory doesn't exist or an error is otherwise encountered, nil is returned.
163
- #
164
- # If a set of path elements is provided, they are appended as-is to the home path if the
165
- # homepath exists.
166
- #
167
- # If an optional block is provided, the joined path is passed to that block if the home path is
168
- # valid and the result of the block is returned instead.
169
- #
170
- # Home-path discovery is performed once. If a path is discovered, that value is memoized so
171
- # that subsequent calls to home_dir don't bounce around.
172
- #
173
- # See self.all_homes.
174
- def self.home(*args)
175
- @@home_dir ||= self.all_homes { |p| break p }
176
- if @@home_dir
177
- path = File.join(@@home_dir, *args)
178
- block_given? ? (yield path) : path
179
- end
180
- end
181
-
182
- # See self.home. This method performs a similar operation except that it yields all the different
183
- # possible values of 'HOME' that one could have on this platform. Hence, on windows, if
184
- # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice.
185
- # This method goes out and checks the existence of each location at the time of the call.
186
- #
187
- # The return is a list of all the returned values from each block invocation or a list of paths
188
- # if no block is provided.
189
- def self.all_homes(*args)
190
- paths = []
191
- if Chef::Platform.windows?
192
- # By default, Ruby uses the the following environment variables to determine Dir.home:
193
- # HOME
194
- # HOMEDRIVE HOMEPATH
195
- # USERPROFILE
196
- # Ruby only checks to see if the variable is specified - not if the directory actually exists.
197
- # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive)
198
- # while USERPROFILE points to the location where the user application settings and profile are stored. HOME
199
- # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is
200
- # HOMESHARE instead of HOMEDRIVE.
201
- #
202
- # We instead walk down the following and only include paths that actually exist.
203
- # HOME
204
- # HOMEDRIVE HOMEPATH
205
- # HOMESHARE HOMEPATH
206
- # USERPROFILE
207
-
208
- paths << ENV['HOME']
209
- paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
210
- paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH']
211
- paths << ENV['USERPROFILE']
212
- end
213
- paths << Dir.home if ENV['HOME']
214
-
215
- # Depending on what environment variables we're using, the slashes can go in any which way.
216
- # Just change them all to / to keep things consistent.
217
- # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on
218
- # the particular brand of kool-aid you consume. This code assumes that \ and / are both
219
- # path separators on any system being used.
220
- paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path }
221
-
222
- # Filter out duplicate paths and paths that don't exist.
223
- valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) }
224
- valid_paths = valid_paths.uniq
225
-
226
- # Join all optional path elements at the end.
227
- # If a block is provided, invoke it - otherwise just return what we've got.
228
- joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) }
229
- if block_given?
230
- joined_paths.each { |p| yield p }
231
- else
232
- joined_paths
233
- end
234
- end
235
-
236
- end
237
- end
238
- end
239
-
240
- # Break a require loop when require chef/util/path_helper
241
- require 'chef/platform'
242
- require 'chef/exceptions'
@@ -1,239 +0,0 @@
1
- #
2
- # Author:: Adam Edwards (<adamed@chef.io>)
3
- # Copyright:: Copyright (c) 2012-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
15
- # implied.
16
- # See the License for the specific language governing permissions and
17
- # limitations under the License.
18
- #
19
-
20
- require 'dummy_winrm_connection'
21
- require 'spec_helper'
22
- require 'tmpdir'
23
-
24
- # These test cases exercise the Knife::Windows knife plugin's ability
25
- # to download a bootstrap msi as part of the bootstrap process on
26
- # Windows nodes. The test modifies the Windows batch file generated
27
- # from an erb template in the plugin source in order to enable execution
28
- # of only the download functionality contained in the bootstrap template.
29
- # The test relies on knowledge of the fields of the template itself and
30
- # also on knowledge of the contents and structure of the Windows batch
31
- # file generated by the template.
32
- #
33
- # Note that if the bootstrap template changes substantially, the tests
34
- # should fail and will require re-implementation. If such changes
35
- # occur, the bootstrap code should be refactored to explicitly expose
36
- # the download funcitonality separately from other tasks to make the
37
- # test more robust.
38
- describe 'Knife::Windows::Core msi download functionality for knife Windows winrm bootstrap template' do
39
-
40
- before(:all) do
41
- # Since we're always running 32-bit Ruby, fix the
42
- # PROCESSOR_ARCHITECTURE environment variable.
43
-
44
- if ENV["PROCESSOR_ARCHITEW6432"]
45
- ENV["PROCESSOR_ARCHITECTURE"] = ENV["PROCESSOR_ARCHITEW6432"]
46
- end
47
-
48
- # All file artifacts from this test will be written into this directory
49
- @temp_directory = Dir.mktmpdir("bootstrap_test")
50
-
51
- # Location to which the download script will be modified to write
52
- # the downloaded msi
53
- @local_file_download_destination = "#{@temp_directory}/chef-client-latest.msi"
54
-
55
- source_code_directory = File.dirname(__FILE__)
56
- @template_file_path ="#{source_code_directory}/../../lib/chef/knife/bootstrap/windows-chef-client-msi.erb"
57
- end
58
-
59
- after(:all) do
60
- # Clear the temp directory upon exit
61
- if Dir.exists?(@temp_directory)
62
- FileUtils::remove_dir(@temp_directory)
63
- end
64
- end
65
-
66
- describe "running on any version of the Windows OS", :windows_only do
67
- let(:mock_bootstrap_context) { Chef::Knife::Core::WindowsBootstrapContext.new({ }, nil, { :knife => {} }) }
68
- let(:mock_winrm) { Chef::Knife::Winrm.new }
69
-
70
- before do
71
- # Stub the bootstrap context and prevent config related sections
72
- # from being populated, i.e. chef installation and first chef
73
- # run sections
74
- allow(mock_bootstrap_context).to receive(:validation_key).and_return("echo.validation_key")
75
- allow(mock_bootstrap_context).to receive(:secret).and_return("echo.encrypted_data_bag_secret")
76
- allow(mock_bootstrap_context).to receive(:config_content).and_return("echo.config_content")
77
- allow(mock_bootstrap_context).to receive(:start_chef).and_return("echo.echo start_chef_command")
78
- allow(mock_bootstrap_context).to receive(:run_list).and_return("echo.run_list")
79
- allow(mock_bootstrap_context).to receive(:install_chef).and_return("echo.echo install_chef_command")
80
-
81
- # Change the directories where bootstrap files will be created
82
- allow(mock_bootstrap_context).to receive(:bootstrap_directory).and_return(@temp_directory.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR))
83
- allow(mock_bootstrap_context).to receive(:local_download_path).and_return(@local_file_download_destination.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR))
84
-
85
- # Prevent password prompt during bootstrap process
86
- allow(mock_winrm.ui).to receive(:ask).and_return(nil)
87
- allow(Chef::Knife::Winrm).to receive(:new).and_return(mock_winrm)
88
-
89
- allow(Chef::Knife::Core::WindowsBootstrapContext).to receive(:new).and_return(mock_bootstrap_context)
90
- Chef::Config[:knife] = {:winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com', :winrm_authentication_protocol => 'negotiate'}
91
- end
92
-
93
- it "downloads the chef-client MSI from the default location during winrm bootstrap" do
94
- run_download_scenario
95
- end
96
-
97
- context "when provided a custom msi_url to fetch from" do
98
- let(:mock_bootstrap_context) { Chef::Knife::Core::WindowsBootstrapContext.new(
99
- { :msi_url => "file:///C:/Windows/System32/xcopy.exe" }, nil, { :knife => {} }) }
100
- it "downloads the chef-client MSI from a custom path during winrm bootstrap" do
101
- run_download_scenario
102
- end
103
- end
104
-
105
- context "when provided a custom msi_url with space in path to fetch from" do
106
- let(:mock_bootstrap_context) { Chef::Knife::Core::WindowsBootstrapContext.new(
107
- { :msi_url => "file:///C:/Program Files/Windows NT/Accessories/wordpad.exe" }, nil, { :knife => {} }) }
108
- it "downloads the chef-client MSI from a custom path with spaces during winrm bootstrap" do
109
- run_download_scenario
110
- end
111
- end
112
- end
113
-
114
- def download_succeeded?
115
- File.exists?(@local_file_download_destination) && ! File.zero?(@local_file_download_destination)
116
- end
117
-
118
- # Remove file artifacts generated by individual test cases
119
- def clean_test_case
120
- if File.exists?(@local_file_download_destination)
121
- File.delete(@local_file_download_destination)
122
- end
123
- end
124
-
125
- def run_download_scenario
126
- clean_test_case
127
-
128
- winrm_bootstrapper = Chef::Knife::BootstrapWindowsWinrm.new([ "127.0.0.1" ])
129
- winrm_bootstrapper.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
130
-
131
- allow(WinRM::Connection).to receive(:new).and_return(Dummy::Connection.new)
132
- allow(winrm_bootstrapper).to receive(:wait_for_remote_response)
133
- allow(winrm_bootstrapper).to receive(:validate_options!)
134
- allow(winrm_bootstrapper.ui).to receive(:ask).and_return(nil)
135
- winrm_bootstrapper.config[:template_file] = @template_file_path
136
- winrm_bootstrapper.config[:run_list] = []
137
- # Execute the commands locally that would normally be executed via WinRM
138
- allow(winrm_bootstrapper).to receive(:run_command) do |command|
139
- system(command)
140
- end
141
-
142
- winrm_bootstrapper.run
143
-
144
- # Download should succeed
145
- expect(download_succeeded?).to be true
146
- end
147
- end
148
-
149
- describe "bootstrap_install_command functionality through WinRM protocol" do
150
- context "bootstrap_install_command option is not specified" do
151
- let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new([]) }
152
- before do
153
- @template_input = sample_data('win_template_unrendered.txt')
154
- @template_output = sample_data('win_template_rendered_without_bootstrap_install_command.txt')
155
- end
156
-
157
- it "bootstrap_install_command option is not rendered in the windows-chef-client-msi.erb template as its value is nil", :chef_lt_12_5_only => true do
158
- expect(bootstrap.send(:render_template,@template_input)).to eq(
159
- @template_output)
160
- end
161
-
162
- context "when running chef-client ~12.5", :chef_gte_12_5_only => true, :chef_lt_13_only => true do
163
- let(:template_12_5_output) { sample_data('win_template_rendered_without_bootstrap_install_command_on_12_5_client.txt') }
164
- it "bootstrap_install_command option is not rendered in the windows-chef-client-msi.erb template as its value is nil" do
165
- expect(bootstrap.send(:render_template,@template_input)).to eq(
166
- template_12_5_output)
167
- end
168
- end
169
-
170
- context "when running chef-client 13.0 or greater", :chef_gte_13_only => true, :chef_lt_14_only => true do
171
- let(:template_13_output) { sample_data('win_template_rendered_without_bootstrap_install_command_on_13_client.txt') }
172
- it "bootstrap_install_command option is not rendered in the windows-chef-client-msi.erb template as its value is nil" do
173
- expect(bootstrap.send(:render_template,@template_input)).to eq(
174
- template_13_output)
175
- end
176
- end
177
- end
178
-
179
- context "bootstrap_install_command option is specified" do
180
- let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['--bootstrap-install-command', 'chef-client -o recipe[cbk1::rec2]']) }
181
- before do
182
- bootstrap.config[:bootstrap_install_command] = "chef-client -o recipe[cbk1::rec2]"
183
- @template_input = sample_data('win_template_unrendered.txt')
184
- @template_output = sample_data('win_template_rendered_with_bootstrap_install_command.txt')
185
- end
186
-
187
- it "bootstrap_install_command option is rendered in the windows-chef-client-msi.erb template", :chef_lt_12_5_only => true do
188
- expect(bootstrap.send(:render_template,@template_input)).to eq(
189
- @template_output)
190
- end
191
-
192
- context "when running chef-client 12.5.0 or greater", :chef_gte_12_5_only => true, :chef_lt_14_only => true do
193
- let(:template_12_5_output) { sample_data('win_template_rendered_with_bootstrap_install_command_on_12_5_client.txt') }
194
- it "bootstrap_install_command option is rendered in the windows-chef-client-msi.erb template" do
195
- expect(bootstrap.send(:render_template,@template_input)).to eq(
196
- template_12_5_output)
197
- end
198
- end
199
-
200
- after do
201
- bootstrap.config.delete(:bootstrap_install_command)
202
- Chef::Config[:knife].delete(:bootstrap_install_command)
203
- end
204
- end
205
- end
206
-
207
- describe "bootstrap_install_command functionality through SSH protocol", :chef_lt_12_5_only => true do
208
- context "bootstrap_install_command option is not specified" do
209
- let(:bootstrap) { Chef::Knife::BootstrapWindowsSsh.new([]) }
210
- before do
211
- @template_input = sample_data('win_template_unrendered.txt')
212
- @template_output = sample_data('win_template_rendered_without_bootstrap_install_command.txt')
213
- end
214
-
215
- it "bootstrap_install_command option is not rendered in the windows-chef-client-msi.erb template as its value is nil" do
216
- expect(bootstrap.send(:render_template,@template_input)).to eq(
217
- @template_output)
218
- end
219
- end
220
-
221
- context "bootstrap_install_command option is specified" do
222
- let(:bootstrap) { Chef::Knife::BootstrapWindowsSsh.new(['--bootstrap-install-command', 'chef-client -o recipe[cbk1::rec2]']) }
223
- before do
224
- bootstrap.config[:bootstrap_install_command] = "chef-client -o recipe[cbk1::rec2]"
225
- @template_input = sample_data('win_template_unrendered.txt')
226
- @template_output = sample_data('win_template_rendered_with_bootstrap_install_command.txt')
227
- end
228
-
229
- it "bootstrap_install_command option is rendered in the windows-chef-client-msi.erb template" do
230
- expect(bootstrap.send(:render_template,@template_input)).to eq(
231
- @template_output)
232
- end
233
-
234
- after do
235
- bootstrap.config.delete(:bootstrap_install_command)
236
- Chef::Config[:knife].delete(:bootstrap_install_command)
237
- end
238
- end
239
- end