knife-windows 1.0.0.rc.1 → 1.0.0.rc.2

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -5
  3. data/.travis.yml +20 -20
  4. data/CHANGELOG.md +75 -74
  5. data/DOC_CHANGES.md +323 -323
  6. data/Gemfile +12 -12
  7. data/LICENSE +201 -201
  8. data/README.md +393 -292
  9. data/RELEASE_NOTES.md +79 -74
  10. data/Rakefile +21 -16
  11. data/appveyor.yml +42 -42
  12. data/ci.gemfile +15 -15
  13. data/features/knife_help.feature +20 -20
  14. data/features/support/env.rb +5 -5
  15. data/knife-windows.gemspec +28 -28
  16. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +247 -241
  17. data/lib/chef/knife/bootstrap_windows_base.rb +388 -368
  18. data/lib/chef/knife/bootstrap_windows_ssh.rb +110 -110
  19. data/lib/chef/knife/bootstrap_windows_winrm.rb +102 -113
  20. data/lib/chef/knife/core/windows_bootstrap_context.rb +361 -362
  21. data/lib/chef/knife/knife_windows_base.rb +33 -0
  22. data/lib/chef/knife/windows_cert_generate.rb +155 -155
  23. data/lib/chef/knife/windows_cert_install.rb +68 -68
  24. data/lib/chef/knife/windows_helper.rb +36 -36
  25. data/lib/chef/knife/windows_listener_create.rb +107 -107
  26. data/lib/chef/knife/winrm.rb +212 -191
  27. data/lib/chef/knife/winrm_base.rb +118 -125
  28. data/lib/chef/knife/winrm_knife_base.rb +218 -201
  29. data/lib/chef/knife/winrm_session.rb +80 -71
  30. data/lib/chef/knife/winrm_shared_options.rb +47 -47
  31. data/lib/chef/knife/wsman_endpoint.rb +44 -44
  32. data/lib/chef/knife/wsman_test.rb +96 -96
  33. data/lib/knife-windows/path_helper.rb +234 -234
  34. data/lib/knife-windows/version.rb +6 -6
  35. data/spec/assets/win_template_rendered_with_bootstrap_install_command.txt +217 -0
  36. data/spec/assets/win_template_rendered_without_bootstrap_install_command.txt +329 -0
  37. data/spec/assets/win_template_unrendered.txt +246 -0
  38. data/spec/functional/bootstrap_download_spec.rb +216 -140
  39. data/spec/spec_helper.rb +87 -72
  40. data/spec/unit/knife/bootstrap_options_spec.rb +146 -146
  41. data/spec/unit/knife/bootstrap_template_spec.rb +92 -92
  42. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +240 -161
  43. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +151 -101
  44. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -90
  45. data/spec/unit/knife/windows_cert_install_spec.rb +51 -51
  46. data/spec/unit/knife/windows_listener_create_spec.rb +76 -76
  47. data/spec/unit/knife/winrm_session_spec.rb +55 -46
  48. data/spec/unit/knife/winrm_spec.rb +504 -376
  49. data/spec/unit/knife/wsman_test_spec.rb +175 -175
  50. metadata +28 -8
@@ -1,368 +1,388 @@
1
- #
2
- # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
- # Copyright:: Copyright (c) 2011 Opscode, 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 'chef/knife'
20
- require 'chef/knife/bootstrap'
21
- require 'chef/encrypted_data_bag_item'
22
- require 'chef/knife/core/windows_bootstrap_context'
23
- # Chef 11 PathHelper doesn't have #home
24
- #require 'chef/util/path_helper'
25
-
26
- class Chef
27
- class Knife
28
- module BootstrapWindowsBase
29
-
30
- # :nodoc:
31
- # Would prefer to do this in a rational way, but can't be done b/c of
32
- # Mixlib::CLI's design :(
33
- def self.included(includer)
34
- includer.class_eval do
35
-
36
- deps do
37
- require 'readline'
38
- require 'chef/json_compat'
39
- end
40
-
41
- option :chef_node_name,
42
- :short => "-N NAME",
43
- :long => "--node-name NAME",
44
- :description => "The Chef node name for your new node"
45
-
46
- option :prerelease,
47
- :long => "--prerelease",
48
- :description => "Install the pre-release chef gems"
49
-
50
- option :bootstrap_version,
51
- :long => "--bootstrap-version VERSION",
52
- :description => "The version of Chef to install",
53
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
54
-
55
- option :bootstrap_proxy,
56
- :long => "--bootstrap-proxy PROXY_URL",
57
- :description => "The proxy server for the node being bootstrapped",
58
- :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
59
-
60
- option :bootstrap_no_proxy,
61
- :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
62
- :description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode",
63
- :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np }
64
-
65
- # DEPR: Remove this option in Chef 13
66
- option :distro,
67
- :short => "-d DISTRO",
68
- :long => "--distro DISTRO",
69
- :description => "Bootstrap a distro using a template. [DEPRECATED] Use -t / --bootstrap-template option instead.",
70
- :proc => Proc.new { |v|
71
- Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use --bootstrap-template option instead.")
72
- v
73
- }
74
-
75
- option :bootstrap_template,
76
- :short => "-t TEMPLATE",
77
- :long => "--bootstrap-template TEMPLATE",
78
- :description => "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
79
-
80
- # DEPR: Remove this option in Chef 13
81
- option :template_file,
82
- :long => "--template-file TEMPLATE",
83
- :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.",
84
- :proc => Proc.new { |v|
85
- Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use --bootstrap-template option instead.")
86
- v
87
- }
88
-
89
- option :run_list,
90
- :short => "-r RUN_LIST",
91
- :long => "--run-list RUN_LIST",
92
- :description => "Comma separated list of roles/recipes to apply",
93
- :proc => lambda { |o| o.split(",") },
94
- :default => []
95
-
96
- option :hint,
97
- :long => "--hint HINT_NAME[=HINT_FILE]",
98
- :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
99
- :proc => Proc.new { |h|
100
- Chef::Config[:knife][:hints] ||= Hash.new
101
- name, path = h.split("=")
102
- Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new
103
- }
104
-
105
- option :first_boot_attributes,
106
- :short => "-j JSON_ATTRIBS",
107
- :long => "--json-attributes",
108
- :description => "A JSON string to be added to the first run of chef-client",
109
- :proc => lambda { |o| JSON.parse(o) },
110
- :default => {}
111
-
112
- # Mismatch between option 'encrypted_data_bag_secret' and it's long value '--secret' is by design for compatibility
113
- option :encrypted_data_bag_secret,
114
- :short => "-s SECRET",
115
- :long => "--secret ",
116
- :description => "The secret key to use to decrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config.",
117
- :default => false
118
-
119
- # Mismatch between option 'encrypted_data_bag_secret_file' and it's long value '--secret-file' is by design for compatibility
120
- option :encrypted_data_bag_secret_file,
121
- :long => "--secret-file SECRET_FILE",
122
- :description => "A file containing the secret key to use to encrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config."
123
-
124
- option :auth_timeout,
125
- :long => "--auth-timeout MINUTES",
126
- :description => "The maximum time in minutes to wait to for authentication over the transport to the node to succeed. The default value is 2 minutes.",
127
- :default => 2
128
-
129
- option :node_ssl_verify_mode,
130
- :long => "--node-ssl-verify-mode [peer|none]",
131
- :description => "Whether or not to verify the SSL cert for all HTTPS requests.",
132
- :proc => Proc.new { |v|
133
- valid_values = ["none", "peer"]
134
- unless valid_values.include?(v)
135
- raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
136
- end
137
- }
138
-
139
- option :node_verify_api_cert,
140
- :long => "--[no-]node-verify-api-cert",
141
- :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
142
- :boolean => true
143
-
144
- option :msi_url,
145
- :short => "-u URL",
146
- :long => "--msi-url URL",
147
- :description => "Location of the Chef Client MSI. The default templates will prefer to download from this location. The MSI will be downloaded from chef.io if not provided.",
148
- :default => ''
149
-
150
- option :install_as_service,
151
- :long => "--install-as-service",
152
- :description => "Install chef-client as service in windows machine",
153
- :default => false
154
-
155
- option :bootstrap_vault_file,
156
- :long => '--bootstrap-vault-file VAULT_FILE',
157
- :description => 'A JSON file with a list of vault(s) and item(s) to be updated'
158
-
159
- option :bootstrap_vault_json,
160
- :long => '--bootstrap-vault-json VAULT_JSON',
161
- :description => 'A JSON string with the vault(s) and item(s) to be updated'
162
-
163
- option :bootstrap_vault_item,
164
- :long => '--bootstrap-vault-item VAULT_ITEM',
165
- :description => 'A single vault and item to update as "vault:item"',
166
- :proc => Proc.new { |i|
167
- (vault, item) = i.split(/:/)
168
- Chef::Config[:knife][:bootstrap_vault_item] ||= {}
169
- Chef::Config[:knife][:bootstrap_vault_item][vault] ||= []
170
- Chef::Config[:knife][:bootstrap_vault_item][vault].push(item)
171
- Chef::Config[:knife][:bootstrap_vault_item]
172
- }
173
- end
174
- end
175
-
176
- def default_bootstrap_template
177
- "windows-chef-client-msi"
178
- end
179
-
180
- def bootstrap_template
181
- # The order here is important. We want to check if we have the new Chef 12 option is set first.
182
- # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at
183
- # the end.
184
- config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template
185
- end
186
-
187
- # TODO: This should go away when CHEF-2193 is fixed
188
- def load_template(template=nil)
189
- # Are we bootstrapping using an already shipped template?
190
-
191
- template = bootstrap_template
192
-
193
- # Use the template directly if it's a path to an actual file
194
- if File.exists?(template)
195
- Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
196
- return IO.read(template).chomp
197
- end
198
-
199
- # Otherwise search the template directories until we find the right one
200
- bootstrap_files = []
201
- bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb")
202
- bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
203
- ::Knife::Windows::PathHelper.all_homes('.chef', 'bootstrap', "#{template}.erb") { |p| bootstrap_files << p }
204
- bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb"))
205
- bootstrap_files.flatten!
206
-
207
- template = Array(bootstrap_files).find do |bootstrap_template|
208
- Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
209
- ::File.exists?(bootstrap_template)
210
- end
211
-
212
- unless template
213
- ui.info("Can not find bootstrap definition for #{config[:distro]}")
214
- raise Errno::ENOENT
215
- end
216
-
217
- Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}")
218
-
219
- IO.read(template).chomp
220
- end
221
-
222
- def bootstrap_context
223
- @bootstrap_context ||= Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config)
224
- end
225
-
226
- def render_template(template=nil)
227
- if config[:secret_file]
228
- config[:secret] = Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
229
- end
230
- Erubis::Eruby.new(template).evaluate(bootstrap_context)
231
- end
232
-
233
- def bootstrap(proto=nil)
234
- if Chef::Config[:knife][:encrypted_data_bag_secret_file] || Chef::Config[:knife][:encrypted_data_bag_secret]
235
- warn_chef_config_secret_key
236
- config[:secret_file] ||= Chef::Config[:knife][:encrypted_data_bag_secret_file]
237
- config[:secret] ||= Chef::Config[:knife][:encrypted_data_bag_secret]
238
- end
239
-
240
- validate_name_args!
241
-
242
- @node_name = Array(@name_args).first
243
- # back compat--templates may use this setting:
244
- config[:server_name] = @node_name
245
-
246
- STDOUT.sync = STDERR.sync = true
247
-
248
- if (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
249
- if Chef::VERSION.split('.').first.to_i == 11
250
- ui.error("Unable to find validation key. Please verify your configuration file for validation_key config value.")
251
- exit 1
252
- end
253
-
254
- unless locate_config_value(:chef_node_name)
255
- ui.error("You must pass a node name with -N when bootstrapping with user credentials")
256
- exit 1
257
- end
258
-
259
- chef_vault_handler.run(node_name: config[:chef_node_name]) if chef_vault_handler.doing_chef_vault?
260
-
261
- client_builder.run
262
- bootstrap_context.client_pem = client_builder.client_path
263
- else
264
- ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...")
265
- ui.info("Delete your validation key in order to use your user credentials instead")
266
- ui.info("")
267
- end
268
-
269
- wait_for_remote_response( config[:auth_timeout].to_i )
270
- ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}")
271
- # create a bootstrap.bat file on the node
272
- # we have to run the remote commands in 2047 char chunks
273
- create_bootstrap_bat_command do |command_chunk|
274
- begin
275
- render_command_result = run_command(command_chunk)
276
- ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0
277
- render_command_result
278
- rescue SystemExit => e
279
- raise unless e.success?
280
- end
281
- end
282
-
283
- # execute the bootstrap.bat file
284
- bootstrap_command_result = run_command(bootstrap_command)
285
- ui.error("Bootstrap command returned #{bootstrap_command_result}") if bootstrap_command_result != 0
286
-
287
- bootstrap_command_result
288
- end
289
-
290
- protected
291
-
292
- # Default implementation -- override only if required by the transport
293
- def wait_for_remote_response(wait_max_minutes)
294
- end
295
-
296
- def bootstrap_command
297
- @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}"
298
- end
299
-
300
- def bootstrap_render_banner_command(chunk_num)
301
- "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}"
302
- end
303
-
304
- def escape_windows_batch_characters(line)
305
- # TODO: The commands are going to get redirected - do we need to escape &?
306
- line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"}
307
- end
308
-
309
- def create_bootstrap_bat_command()
310
- chunk_num = 0
311
- bootstrap_bat = ""
312
- banner = bootstrap_render_banner_command(chunk_num += 1)
313
- render_template(load_template(config[:bootstrap_template])).each_line do |line|
314
- escape_windows_batch_characters(line)
315
- # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can
316
- # confidently prefix every actual command with &&.
317
- # TODO: Why does ^\n&& work directly through the commandline but not through SOAP?
318
- render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})"
319
- # Windows commands are limited to 8191 characters for machines running XP or higher but
320
- # this includes the length of environment variables after they have been expanded.
321
- # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner
322
- # and once in every command redirection), we simply guess and set the max to 5000.
323
- # TODO: When a more accurate method is available, fix this.
324
- if bootstrap_bat.length + render_line.length + banner.length > 5000
325
- # Can't fit it into this chunk? - flush (if necessary) and then try.
326
- # Do this first because banner.length might change (e.g. due to an extra digit) and
327
- # prevent a fit.
328
- unless bootstrap_bat.empty?
329
- yield banner + bootstrap_bat
330
- bootstrap_bat = ""
331
- banner = bootstrap_render_banner_command(chunk_num += 1)
332
- end
333
- # Will this ever fit?
334
- if render_line.length + banner.length > 5000
335
- raise "Command in bootstrap template too long by #{render_line.length + banner.length - 5000} characters : #{line}"
336
- end
337
- end
338
- bootstrap_bat << render_line
339
- end
340
- raise "Bootstrap template was empty! Check #{config[:bootstrap_template]}" if bootstrap_bat.empty?
341
- yield banner + bootstrap_bat
342
- end
343
-
344
- def bootstrap_bat_file
345
- @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\""
346
- end
347
-
348
- def locate_config_value(key)
349
- key = key.to_sym
350
- config[key] || Chef::Config[:knife][key]
351
- end
352
-
353
- def warn_chef_config_secret_key
354
- ui.info "* " * 40
355
- ui.warn(<<-WARNING)
356
- \nSpecifying the encrypted data bag secret key using an 'encrypted_data_bag_secret'
357
- entry in 'knife.rb' is deprecated. Please use the '--secret' or '--secret-file'
358
- options of this command instead.
359
-
360
- #{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this
361
- behavior will be removed and any 'encrypted_data_bag_secret' entries in
362
- 'knife.rb' will be ignored completely.
363
- WARNING
364
- ui.info "* " * 40
365
- end
366
- end
367
- end
368
- end
1
+ #
2
+ # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
+ # Copyright:: Copyright (c) 2011 Opscode, 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 'chef/knife'
20
+ require 'chef/knife/bootstrap'
21
+ require 'chef/encrypted_data_bag_item'
22
+ require 'chef/knife/core/windows_bootstrap_context'
23
+ require 'chef/knife/knife_windows_base'
24
+ # Chef 11 PathHelper doesn't have #home
25
+ #require 'chef/util/path_helper'
26
+
27
+ class Chef
28
+ class Knife
29
+ module BootstrapWindowsBase
30
+
31
+ include Chef::Knife::KnifeWindowsBase
32
+
33
+ # :nodoc:
34
+ # Would prefer to do this in a rational way, but can't be done b/c of
35
+ # Mixlib::CLI's design :(
36
+ def self.included(includer)
37
+ includer.class_eval do
38
+
39
+ deps do
40
+ require 'readline'
41
+ require 'chef/json_compat'
42
+ end
43
+
44
+ option :chef_node_name,
45
+ :short => "-N NAME",
46
+ :long => "--node-name NAME",
47
+ :description => "The Chef node name for your new node"
48
+
49
+ option :prerelease,
50
+ :long => "--prerelease",
51
+ :description => "Install the pre-release chef gems"
52
+
53
+ option :bootstrap_version,
54
+ :long => "--bootstrap-version VERSION",
55
+ :description => "The version of Chef to install",
56
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
57
+
58
+ option :bootstrap_proxy,
59
+ :long => "--bootstrap-proxy PROXY_URL",
60
+ :description => "The proxy server for the node being bootstrapped",
61
+ :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
62
+
63
+ option :bootstrap_no_proxy,
64
+ :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
65
+ :description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode",
66
+ :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np }
67
+
68
+ option :bootstrap_install_command,
69
+ :long => "--bootstrap-install-command COMMANDS",
70
+ :description => "Custom command to install chef-client",
71
+ :proc => Proc.new { |ic| Chef::Config[:knife][:bootstrap_install_command] = ic }
72
+
73
+ # DEPR: Remove this option in Chef 13
74
+ option :distro,
75
+ :short => "-d DISTRO",
76
+ :long => "--distro DISTRO",
77
+ :description => "Bootstrap a distro using a template. [DEPRECATED] Use -t / --bootstrap-template option instead.",
78
+ :proc => Proc.new { |v|
79
+ Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use --bootstrap-template option instead.")
80
+ v
81
+ }
82
+
83
+ option :bootstrap_template,
84
+ :short => "-t TEMPLATE",
85
+ :long => "--bootstrap-template TEMPLATE",
86
+ :description => "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
87
+
88
+ # DEPR: Remove this option in Chef 13
89
+ option :template_file,
90
+ :long => "--template-file TEMPLATE",
91
+ :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.",
92
+ :proc => Proc.new { |v|
93
+ Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use --bootstrap-template option instead.")
94
+ v
95
+ }
96
+
97
+ option :run_list,
98
+ :short => "-r RUN_LIST",
99
+ :long => "--run-list RUN_LIST",
100
+ :description => "Comma separated list of roles/recipes to apply",
101
+ :proc => lambda { |o| o.split(",") },
102
+ :default => []
103
+
104
+ option :hint,
105
+ :long => "--hint HINT_NAME[=HINT_FILE]",
106
+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
107
+ :proc => Proc.new { |h|
108
+ Chef::Config[:knife][:hints] ||= Hash.new
109
+ name, path = h.split("=")
110
+ Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new
111
+ }
112
+
113
+ option :first_boot_attributes,
114
+ :short => "-j JSON_ATTRIBS",
115
+ :long => "--json-attributes",
116
+ :description => "A JSON string to be added to the first run of chef-client",
117
+ :proc => lambda { |o| JSON.parse(o) },
118
+ :default => {}
119
+
120
+ # Mismatch between option 'encrypted_data_bag_secret' and it's long value '--secret' is by design for compatibility
121
+ option :encrypted_data_bag_secret,
122
+ :short => "-s SECRET",
123
+ :long => "--secret ",
124
+ :description => "The secret key to use to decrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config.",
125
+ :default => false
126
+
127
+ # Mismatch between option 'encrypted_data_bag_secret_file' and it's long value '--secret-file' is by design for compatibility
128
+ option :encrypted_data_bag_secret_file,
129
+ :long => "--secret-file SECRET_FILE",
130
+ :description => "A file containing the secret key to use to encrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config."
131
+
132
+ option :auth_timeout,
133
+ :long => "--auth-timeout MINUTES",
134
+ :description => "The maximum time in minutes to wait to for authentication over the transport to the node to succeed. The default value is 2 minutes.",
135
+ :default => 2
136
+
137
+ option :node_ssl_verify_mode,
138
+ :long => "--node-ssl-verify-mode [peer|none]",
139
+ :description => "Whether or not to verify the SSL cert for all HTTPS requests.",
140
+ :proc => Proc.new { |v|
141
+ valid_values = ["none", "peer"]
142
+ unless valid_values.include?(v)
143
+ raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
144
+ end
145
+ }
146
+
147
+ option :node_verify_api_cert,
148
+ :long => "--[no-]node-verify-api-cert",
149
+ :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
150
+ :boolean => true
151
+
152
+ option :msi_url,
153
+ :short => "-u URL",
154
+ :long => "--msi-url URL",
155
+ :description => "Location of the Chef Client MSI. The default templates will prefer to download from this location. The MSI will be downloaded from chef.io if not provided.",
156
+ :default => ''
157
+
158
+ option :install_as_service,
159
+ :long => "--install-as-service",
160
+ :description => "Install chef-client as a Windows service",
161
+ :default => false
162
+
163
+ option :bootstrap_vault_file,
164
+ :long => '--bootstrap-vault-file VAULT_FILE',
165
+ :description => 'A JSON file with a list of vault(s) and item(s) to be updated'
166
+
167
+ option :bootstrap_vault_json,
168
+ :long => '--bootstrap-vault-json VAULT_JSON',
169
+ :description => 'A JSON string with the vault(s) and item(s) to be updated'
170
+
171
+ option :bootstrap_vault_item,
172
+ :long => '--bootstrap-vault-item VAULT_ITEM',
173
+ :description => 'A single vault and item to update as "vault:item"',
174
+ :proc => Proc.new { |i|
175
+ (vault, item) = i.split(/:/)
176
+ Chef::Config[:knife][:bootstrap_vault_item] ||= {}
177
+ Chef::Config[:knife][:bootstrap_vault_item][vault] ||= []
178
+ Chef::Config[:knife][:bootstrap_vault_item][vault].push(item)
179
+ Chef::Config[:knife][:bootstrap_vault_item]
180
+ }
181
+
182
+ option :tags,
183
+ :long => "--tags TAGS",
184
+ :description => "Comma separated list of tags to apply to the node",
185
+ :proc => lambda { |o| o.split(/[\s,]+/) },
186
+ :default => []
187
+ end
188
+ end
189
+
190
+ def default_bootstrap_template
191
+ "windows-chef-client-msi"
192
+ end
193
+
194
+ def bootstrap_template
195
+ # The order here is important. We want to check if we have the new Chef 12 option is set first.
196
+ # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at
197
+ # the end.
198
+ config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template
199
+ end
200
+
201
+ # TODO: This should go away when CHEF-2193 is fixed
202
+ def load_template(template=nil)
203
+ # Are we bootstrapping using an already shipped template?
204
+
205
+ template = bootstrap_template
206
+
207
+ # Use the template directly if it's a path to an actual file
208
+ if File.exists?(template)
209
+ Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
210
+ return IO.read(template).chomp
211
+ end
212
+
213
+ # Otherwise search the template directories until we find the right one
214
+ bootstrap_files = []
215
+ bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb")
216
+ bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
217
+ ::Knife::Windows::PathHelper.all_homes('.chef', 'bootstrap', "#{template}.erb") { |p| bootstrap_files << p }
218
+ bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb"))
219
+ bootstrap_files.flatten!
220
+
221
+ template = Array(bootstrap_files).find do |bootstrap_template|
222
+ Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
223
+ ::File.exists?(bootstrap_template)
224
+ end
225
+
226
+ unless template
227
+ ui.info("Can not find bootstrap definition for #{config[:distro]}")
228
+ raise Errno::ENOENT
229
+ end
230
+
231
+ Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}")
232
+
233
+ IO.read(template).chomp
234
+ end
235
+
236
+ def bootstrap_context
237
+ @bootstrap_context ||= Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config)
238
+ end
239
+
240
+ def load_correct_secret
241
+ knife_secret_file = Chef::Config[:knife][:encrypted_data_bag_secret_file]
242
+ knife_secret = Chef::Config[:knife][:encrypted_data_bag_secret]
243
+ cli_secret_file = config[:encrypted_data_bag_secret_file]
244
+ cli_secret = config[:encrypted_data_bag_secret]
245
+
246
+ cli_secret_file = nil if cli_secret_file == knife_secret_file
247
+ cli_secret = nil if cli_secret == knife_secret
248
+
249
+ cli_secret_file = Chef::EncryptedDataBagItem.load_secret(cli_secret_file) if cli_secret_file != nil
250
+ knife_secret_file = Chef::EncryptedDataBagItem.load_secret(knife_secret_file) if knife_secret_file != nil
251
+
252
+ cli_secret_file || cli_secret || knife_secret_file || knife_secret
253
+ end
254
+
255
+ def render_template(template=nil)
256
+ config[:secret] = load_correct_secret
257
+ Erubis::Eruby.new(template).evaluate(bootstrap_context)
258
+ end
259
+
260
+ def bootstrap(proto=nil)
261
+ if Chef::Config[:knife][:encrypted_data_bag_secret_file] || Chef::Config[:knife][:encrypted_data_bag_secret]
262
+ warn_chef_config_secret_key
263
+ end
264
+
265
+ validate_name_args!
266
+
267
+ @node_name = Array(@name_args).first
268
+ # back compat--templates may use this setting:
269
+ config[:server_name] = @node_name
270
+
271
+ STDOUT.sync = STDERR.sync = true
272
+
273
+ if (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
274
+ if Chef::VERSION.split('.').first.to_i == 11
275
+ ui.error("Unable to find validation key. Please verify your configuration file for validation_key config value.")
276
+ exit 1
277
+ end
278
+
279
+ unless locate_config_value(:chef_node_name)
280
+ ui.error("You must pass a node name with -N when bootstrapping with user credentials")
281
+ exit 1
282
+ end
283
+
284
+ chef_vault_handler.run(node_name: config[:chef_node_name]) if chef_vault_handler.doing_chef_vault?
285
+
286
+ client_builder.run
287
+ bootstrap_context.client_pem = client_builder.client_path
288
+ else
289
+ ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...")
290
+ ui.info("Delete your validation key in order to use your user credentials instead")
291
+ ui.info("")
292
+ end
293
+
294
+ wait_for_remote_response( config[:auth_timeout].to_i )
295
+ ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}")
296
+ # create a bootstrap.bat file on the node
297
+ # we have to run the remote commands in 2047 char chunks
298
+ create_bootstrap_bat_command do |command_chunk|
299
+ begin
300
+ render_command_result = run_command(command_chunk)
301
+ ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0
302
+ render_command_result
303
+ rescue SystemExit => e
304
+ raise unless e.success?
305
+ end
306
+ end
307
+
308
+ # execute the bootstrap.bat file
309
+ bootstrap_command_result = run_command(bootstrap_command)
310
+ ui.error("Bootstrap command returned #{bootstrap_command_result}") if bootstrap_command_result != 0
311
+
312
+ bootstrap_command_result
313
+ end
314
+
315
+ protected
316
+
317
+ # Default implementation -- override only if required by the transport
318
+ def wait_for_remote_response(wait_max_minutes)
319
+ end
320
+
321
+ def bootstrap_command
322
+ @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}"
323
+ end
324
+
325
+ def bootstrap_render_banner_command(chunk_num)
326
+ "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}"
327
+ end
328
+
329
+ def escape_windows_batch_characters(line)
330
+ # TODO: The commands are going to get redirected - do we need to escape &?
331
+ line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"}
332
+ end
333
+
334
+ def create_bootstrap_bat_command()
335
+ chunk_num = 0
336
+ bootstrap_bat = ""
337
+ banner = bootstrap_render_banner_command(chunk_num += 1)
338
+ render_template(load_template(config[:bootstrap_template])).each_line do |line|
339
+ escape_windows_batch_characters(line)
340
+ # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can
341
+ # confidently prefix every actual command with &&.
342
+ # TODO: Why does ^\n&& work directly through the commandline but not through SOAP?
343
+ render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})"
344
+ # Windows commands are limited to 8191 characters for machines running XP or higher but
345
+ # this includes the length of environment variables after they have been expanded.
346
+ # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner
347
+ # and once in every command redirection), we simply guess and set the max to 5000.
348
+ # TODO: When a more accurate method is available, fix this.
349
+ if bootstrap_bat.length + render_line.length + banner.length > 5000
350
+ # Can't fit it into this chunk? - flush (if necessary) and then try.
351
+ # Do this first because banner.length might change (e.g. due to an extra digit) and
352
+ # prevent a fit.
353
+ unless bootstrap_bat.empty?
354
+ yield banner + bootstrap_bat
355
+ bootstrap_bat = ""
356
+ banner = bootstrap_render_banner_command(chunk_num += 1)
357
+ end
358
+ # Will this ever fit?
359
+ if render_line.length + banner.length > 5000
360
+ raise "Command in bootstrap template too long by #{render_line.length + banner.length - 5000} characters : #{line}"
361
+ end
362
+ end
363
+ bootstrap_bat << render_line
364
+ end
365
+ raise "Bootstrap template was empty! Check #{config[:bootstrap_template]}" if bootstrap_bat.empty?
366
+ yield banner + bootstrap_bat
367
+ end
368
+
369
+ def bootstrap_bat_file
370
+ @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\""
371
+ end
372
+
373
+ def warn_chef_config_secret_key
374
+ ui.info "* " * 40
375
+ ui.warn(<<-WARNING)
376
+ \nSpecifying the encrypted data bag secret key using an 'encrypted_data_bag_secret'
377
+ entry in 'knife.rb' is deprecated. Please use the '--secret' or '--secret-file'
378
+ options of this command instead.
379
+
380
+ #{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this
381
+ behavior will be removed and any 'encrypted_data_bag_secret' entries in
382
+ 'knife.rb' will be ignored completely.
383
+ WARNING
384
+ ui.info "* " * 40
385
+ end
386
+ end
387
+ end
388
+ end