knife-windows 1.1.0 → 1.1.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -5
  3. data/.travis.yml +20 -20
  4. data/CHANGELOG.md +87 -83
  5. data/DOC_CHANGES.md +20 -20
  6. data/Gemfile +12 -12
  7. data/LICENSE +201 -201
  8. data/README.md +396 -396
  9. data/RELEASE_NOTES.md +34 -34
  10. data/Rakefile +21 -21
  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 -247
  17. data/lib/chef/knife/bootstrap_windows_base.rb +407 -401
  18. data/lib/chef/knife/bootstrap_windows_ssh.rb +110 -110
  19. data/lib/chef/knife/bootstrap_windows_winrm.rb +95 -102
  20. data/lib/chef/knife/core/windows_bootstrap_context.rb +362 -362
  21. data/lib/chef/knife/knife_windows_base.rb +33 -33
  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 +122 -212
  27. data/lib/chef/knife/winrm_base.rb +118 -118
  28. data/lib/chef/knife/winrm_knife_base.rb +309 -218
  29. data/lib/chef/knife/winrm_session.rb +82 -82
  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 +95 -95
  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 -217
  36. data/spec/assets/win_template_rendered_with_bootstrap_install_command_on_12_5_client.txt +217 -217
  37. data/spec/assets/win_template_rendered_without_bootstrap_install_command.txt +329 -329
  38. data/spec/assets/win_template_rendered_without_bootstrap_install_command_on_12_5_client.txt +329 -329
  39. data/spec/assets/win_template_unrendered.txt +246 -246
  40. data/spec/functional/bootstrap_download_spec.rb +234 -233
  41. data/spec/spec_helper.rb +88 -88
  42. data/spec/unit/knife/bootstrap_options_spec.rb +148 -146
  43. data/spec/unit/knife/bootstrap_template_spec.rb +92 -92
  44. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +259 -243
  45. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +151 -151
  46. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -90
  47. data/spec/unit/knife/windows_cert_install_spec.rb +51 -51
  48. data/spec/unit/knife/windows_listener_create_spec.rb +76 -76
  49. data/spec/unit/knife/winrm_session_spec.rb +73 -73
  50. data/spec/unit/knife/winrm_spec.rb +551 -504
  51. data/spec/unit/knife/wsman_test_spec.rb +178 -175
  52. metadata +3 -23
@@ -1,401 +1,407 @@
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 :policy_name,
183
- :long => "--policy-name POLICY_NAME",
184
- :description => "Policyfile name to use (--policy-group must also be given)",
185
- :default => nil
186
-
187
- option :policy_group,
188
- :long => "--policy-group POLICY_GROUP",
189
- :description => "Policy group name to use (--policy-name must also be given)",
190
- :default => nil
191
-
192
- option :tags,
193
- :long => "--tags TAGS",
194
- :description => "Comma separated list of tags to apply to the node",
195
- :proc => lambda { |o| o.split(/[\s,]+/) },
196
- :default => []
197
- end
198
- end
199
-
200
- def default_bootstrap_template
201
- "windows-chef-client-msi"
202
- end
203
-
204
- def bootstrap_template
205
- # The order here is important. We want to check if we have the new Chef 12 option is set first.
206
- # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at
207
- # the end.
208
- config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template
209
- end
210
-
211
- # TODO: This should go away when CHEF-2193 is fixed
212
- def load_template(template=nil)
213
- # Are we bootstrapping using an already shipped template?
214
-
215
- template = bootstrap_template
216
-
217
- # Use the template directly if it's a path to an actual file
218
- if File.exists?(template)
219
- Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
220
- return IO.read(template).chomp
221
- end
222
-
223
- # Otherwise search the template directories until we find the right one
224
- bootstrap_files = []
225
- bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb")
226
- bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
227
- ::Knife::Windows::PathHelper.all_homes('.chef', 'bootstrap', "#{template}.erb") { |p| bootstrap_files << p }
228
- bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb"))
229
- bootstrap_files.flatten!
230
-
231
- template = Array(bootstrap_files).find do |bootstrap_template|
232
- Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
233
- ::File.exists?(bootstrap_template)
234
- end
235
-
236
- unless template
237
- ui.info("Can not find bootstrap definition for #{config[:distro]}")
238
- raise Errno::ENOENT
239
- end
240
-
241
- Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}")
242
-
243
- IO.read(template).chomp
244
- end
245
-
246
- def bootstrap_context
247
- @bootstrap_context ||= Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config)
248
- end
249
-
250
- def load_correct_secret
251
- knife_secret_file = Chef::Config[:knife][:encrypted_data_bag_secret_file]
252
- knife_secret = Chef::Config[:knife][:encrypted_data_bag_secret]
253
- cli_secret_file = config[:encrypted_data_bag_secret_file]
254
- cli_secret = config[:encrypted_data_bag_secret]
255
-
256
- cli_secret_file = nil if cli_secret_file == knife_secret_file
257
- cli_secret = nil if cli_secret == knife_secret
258
-
259
- cli_secret_file = Chef::EncryptedDataBagItem.load_secret(cli_secret_file) if cli_secret_file != nil
260
- knife_secret_file = Chef::EncryptedDataBagItem.load_secret(knife_secret_file) if knife_secret_file != nil
261
-
262
- cli_secret_file || cli_secret || knife_secret_file || knife_secret
263
- end
264
-
265
- def render_template(template=nil)
266
- config[:secret] = load_correct_secret
267
- Erubis::Eruby.new(template).evaluate(bootstrap_context)
268
- end
269
-
270
- def bootstrap(proto=nil)
271
- if Chef::Config[:knife][:encrypted_data_bag_secret_file] || Chef::Config[:knife][:encrypted_data_bag_secret]
272
- warn_chef_config_secret_key
273
- end
274
-
275
- validate_name_args!
276
-
277
- # adding respond_to? so this works with pre 12.4 chef clients
278
- validate_options! if respond_to?(:validate_options!)
279
-
280
- @node_name = Array(@name_args).first
281
- # back compat--templates may use this setting:
282
- config[:server_name] = @node_name
283
-
284
- STDOUT.sync = STDERR.sync = true
285
-
286
- if (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
287
- if Chef::VERSION.split('.').first.to_i == 11
288
- ui.error("Unable to find validation key. Please verify your configuration file for validation_key config value.")
289
- exit 1
290
- end
291
-
292
- unless locate_config_value(:chef_node_name)
293
- ui.error("You must pass a node name with -N when bootstrapping with user credentials")
294
- exit 1
295
- end
296
-
297
- chef_vault_handler.run(node_name: config[:chef_node_name]) if chef_vault_handler.doing_chef_vault?
298
-
299
- client_builder.run
300
- bootstrap_context.client_pem = client_builder.client_path
301
- else
302
- ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...")
303
- ui.info("Delete your validation key in order to use your user credentials instead")
304
- ui.info("")
305
- end
306
-
307
- wait_for_remote_response( config[:auth_timeout].to_i )
308
- ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}")
309
- # create a bootstrap.bat file on the node
310
- # we have to run the remote commands in 2047 char chunks
311
- create_bootstrap_bat_command do |command_chunk|
312
- begin
313
- render_command_result = run_command(command_chunk)
314
- ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0
315
- render_command_result
316
- rescue SystemExit => e
317
- raise unless e.success?
318
- end
319
- end
320
-
321
- # execute the bootstrap.bat file
322
- bootstrap_command_result = run_command(bootstrap_command)
323
- ui.error("Bootstrap command returned #{bootstrap_command_result}") if bootstrap_command_result != 0
324
-
325
- bootstrap_command_result
326
- end
327
-
328
- protected
329
-
330
- # Default implementation -- override only if required by the transport
331
- def wait_for_remote_response(wait_max_minutes)
332
- end
333
-
334
- def bootstrap_command
335
- @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}"
336
- end
337
-
338
- def bootstrap_render_banner_command(chunk_num)
339
- "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}"
340
- end
341
-
342
- def escape_windows_batch_characters(line)
343
- # TODO: The commands are going to get redirected - do we need to escape &?
344
- line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"}
345
- end
346
-
347
- def create_bootstrap_bat_command()
348
- chunk_num = 0
349
- bootstrap_bat = ""
350
- banner = bootstrap_render_banner_command(chunk_num += 1)
351
- render_template(load_template(config[:bootstrap_template])).each_line do |line|
352
- escape_windows_batch_characters(line)
353
- # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can
354
- # confidently prefix every actual command with &&.
355
- # TODO: Why does ^\n&& work directly through the commandline but not through SOAP?
356
- render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})"
357
- # Windows commands are limited to 8191 characters for machines running XP or higher but
358
- # this includes the length of environment variables after they have been expanded.
359
- # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner
360
- # and once in every command redirection), we simply guess and set the max to 5000.
361
- # TODO: When a more accurate method is available, fix this.
362
- if bootstrap_bat.length + render_line.length + banner.length > 5000
363
- # Can't fit it into this chunk? - flush (if necessary) and then try.
364
- # Do this first because banner.length might change (e.g. due to an extra digit) and
365
- # prevent a fit.
366
- unless bootstrap_bat.empty?
367
- yield banner + bootstrap_bat
368
- bootstrap_bat = ""
369
- banner = bootstrap_render_banner_command(chunk_num += 1)
370
- end
371
- # Will this ever fit?
372
- if render_line.length + banner.length > 5000
373
- raise "Command in bootstrap template too long by #{render_line.length + banner.length - 5000} characters : #{line}"
374
- end
375
- end
376
- bootstrap_bat << render_line
377
- end
378
- raise "Bootstrap template was empty! Check #{config[:bootstrap_template]}" if bootstrap_bat.empty?
379
- yield banner + bootstrap_bat
380
- end
381
-
382
- def bootstrap_bat_file
383
- @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\""
384
- end
385
-
386
- def warn_chef_config_secret_key
387
- ui.info "* " * 40
388
- ui.warn(<<-WARNING)
389
- \nSpecifying the encrypted data bag secret key using an 'encrypted_data_bag_secret'
390
- entry in 'knife.rb' is deprecated. Please use the '--secret' or '--secret-file'
391
- options of this command instead.
392
-
393
- #{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this
394
- behavior will be removed and any 'encrypted_data_bag_secret' entries in
395
- 'knife.rb' will be ignored completely.
396
- WARNING
397
- ui.info "* " * 40
398
- end
399
- end
400
- end
401
- 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 => nil
119
+
120
+ option :first_boot_attributes_from_file,
121
+ :long => "--json-attribute-file FILE",
122
+ :description => "A JSON file to be used to the first run of chef-client",
123
+ :proc => lambda { |o| Chef::JSONCompat.parse(File.read(o)) },
124
+ :default => nil
125
+
126
+ # Mismatch between option 'encrypted_data_bag_secret' and it's long value '--secret' is by design for compatibility
127
+ option :encrypted_data_bag_secret,
128
+ :short => "-s SECRET",
129
+ :long => "--secret ",
130
+ :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.",
131
+ :default => false
132
+
133
+ # Mismatch between option 'encrypted_data_bag_secret_file' and it's long value '--secret-file' is by design for compatibility
134
+ option :encrypted_data_bag_secret_file,
135
+ :long => "--secret-file SECRET_FILE",
136
+ :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."
137
+
138
+ option :auth_timeout,
139
+ :long => "--auth-timeout MINUTES",
140
+ :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.",
141
+ :default => 2
142
+
143
+ option :node_ssl_verify_mode,
144
+ :long => "--node-ssl-verify-mode [peer|none]",
145
+ :description => "Whether or not to verify the SSL cert for all HTTPS requests.",
146
+ :proc => Proc.new { |v|
147
+ valid_values = ["none", "peer"]
148
+ unless valid_values.include?(v)
149
+ raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
150
+ end
151
+ }
152
+
153
+ option :node_verify_api_cert,
154
+ :long => "--[no-]node-verify-api-cert",
155
+ :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
156
+ :boolean => true
157
+
158
+ option :msi_url,
159
+ :short => "-u URL",
160
+ :long => "--msi-url URL",
161
+ :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.",
162
+ :default => ''
163
+
164
+ option :install_as_service,
165
+ :long => "--install-as-service",
166
+ :description => "Install chef-client as a Windows service",
167
+ :default => false
168
+
169
+ option :bootstrap_vault_file,
170
+ :long => '--bootstrap-vault-file VAULT_FILE',
171
+ :description => 'A JSON file with a list of vault(s) and item(s) to be updated'
172
+
173
+ option :bootstrap_vault_json,
174
+ :long => '--bootstrap-vault-json VAULT_JSON',
175
+ :description => 'A JSON string with the vault(s) and item(s) to be updated'
176
+
177
+ option :bootstrap_vault_item,
178
+ :long => '--bootstrap-vault-item VAULT_ITEM',
179
+ :description => 'A single vault and item to update as "vault:item"',
180
+ :proc => Proc.new { |i|
181
+ (vault, item) = i.split(/:/)
182
+ Chef::Config[:knife][:bootstrap_vault_item] ||= {}
183
+ Chef::Config[:knife][:bootstrap_vault_item][vault] ||= []
184
+ Chef::Config[:knife][:bootstrap_vault_item][vault].push(item)
185
+ Chef::Config[:knife][:bootstrap_vault_item]
186
+ }
187
+
188
+ option :policy_name,
189
+ :long => "--policy-name POLICY_NAME",
190
+ :description => "Policyfile name to use (--policy-group must also be given)",
191
+ :default => nil
192
+
193
+ option :policy_group,
194
+ :long => "--policy-group POLICY_GROUP",
195
+ :description => "Policy group name to use (--policy-name must also be given)",
196
+ :default => nil
197
+
198
+ option :tags,
199
+ :long => "--tags TAGS",
200
+ :description => "Comma separated list of tags to apply to the node",
201
+ :proc => lambda { |o| o.split(/[\s,]+/) },
202
+ :default => []
203
+ end
204
+ end
205
+
206
+ def default_bootstrap_template
207
+ "windows-chef-client-msi"
208
+ end
209
+
210
+ def bootstrap_template
211
+ # The order here is important. We want to check if we have the new Chef 12 option is set first.
212
+ # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at
213
+ # the end.
214
+ config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template
215
+ end
216
+
217
+ # TODO: This should go away when CHEF-2193 is fixed
218
+ def load_template(template=nil)
219
+ # Are we bootstrapping using an already shipped template?
220
+
221
+ template = bootstrap_template
222
+
223
+ # Use the template directly if it's a path to an actual file
224
+ if File.exists?(template)
225
+ Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
226
+ return IO.read(template).chomp
227
+ end
228
+
229
+ # Otherwise search the template directories until we find the right one
230
+ bootstrap_files = []
231
+ bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb")
232
+ bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
233
+ ::Knife::Windows::PathHelper.all_homes('.chef', 'bootstrap', "#{template}.erb") { |p| bootstrap_files << p }
234
+ bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb"))
235
+ bootstrap_files.flatten!
236
+
237
+ template = Array(bootstrap_files).find do |bootstrap_template|
238
+ Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
239
+ ::File.exists?(bootstrap_template)
240
+ end
241
+
242
+ unless template
243
+ ui.info("Can not find bootstrap definition for #{config[:distro]}")
244
+ raise Errno::ENOENT
245
+ end
246
+
247
+ Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}")
248
+
249
+ IO.read(template).chomp
250
+ end
251
+
252
+ def bootstrap_context
253
+ @bootstrap_context ||= Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config)
254
+ end
255
+
256
+ def load_correct_secret
257
+ knife_secret_file = Chef::Config[:knife][:encrypted_data_bag_secret_file]
258
+ knife_secret = Chef::Config[:knife][:encrypted_data_bag_secret]
259
+ cli_secret_file = config[:encrypted_data_bag_secret_file]
260
+ cli_secret = config[:encrypted_data_bag_secret]
261
+
262
+ cli_secret_file = nil if cli_secret_file == knife_secret_file
263
+ cli_secret = nil if cli_secret == knife_secret
264
+
265
+ cli_secret_file = Chef::EncryptedDataBagItem.load_secret(cli_secret_file) if cli_secret_file != nil
266
+ knife_secret_file = Chef::EncryptedDataBagItem.load_secret(knife_secret_file) if knife_secret_file != nil
267
+
268
+ cli_secret_file || cli_secret || knife_secret_file || knife_secret
269
+ end
270
+
271
+ def render_template(template=nil)
272
+ config[:secret] = load_correct_secret
273
+ Erubis::Eruby.new(template).evaluate(bootstrap_context)
274
+ end
275
+
276
+ def bootstrap(proto=nil)
277
+ if Chef::Config[:knife][:encrypted_data_bag_secret_file] || Chef::Config[:knife][:encrypted_data_bag_secret]
278
+ warn_chef_config_secret_key
279
+ end
280
+
281
+ validate_name_args!
282
+
283
+ # adding respond_to? so this works with pre 12.4 chef clients
284
+ validate_options! if respond_to?(:validate_options!)
285
+
286
+ @node_name = Array(@name_args).first
287
+ # back compat--templates may use this setting:
288
+ config[:server_name] = @node_name
289
+
290
+ STDOUT.sync = STDERR.sync = true
291
+
292
+ if (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
293
+ if Chef::VERSION.split('.').first.to_i == 11
294
+ ui.error("Unable to find validation key. Please verify your configuration file for validation_key config value.")
295
+ exit 1
296
+ end
297
+
298
+ unless locate_config_value(:chef_node_name)
299
+ ui.error("You must pass a node name with -N when bootstrapping with user credentials")
300
+ exit 1
301
+ end
302
+
303
+ chef_vault_handler.run(node_name: config[:chef_node_name]) if chef_vault_handler.doing_chef_vault?
304
+
305
+ client_builder.run
306
+ bootstrap_context.client_pem = client_builder.client_path
307
+ else
308
+ ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...")
309
+ ui.info("Delete your validation key in order to use your user credentials instead")
310
+ ui.info("")
311
+ end
312
+
313
+ wait_for_remote_response( config[:auth_timeout].to_i )
314
+ ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}")
315
+ # create a bootstrap.bat file on the node
316
+ # we have to run the remote commands in 2047 char chunks
317
+ create_bootstrap_bat_command do |command_chunk|
318
+ begin
319
+ render_command_result = run_command(command_chunk)
320
+ ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0
321
+ render_command_result
322
+ rescue SystemExit => e
323
+ raise unless e.success?
324
+ end
325
+ end
326
+
327
+ # execute the bootstrap.bat file
328
+ bootstrap_command_result = run_command(bootstrap_command)
329
+ ui.error("Bootstrap command returned #{bootstrap_command_result}") if bootstrap_command_result != 0
330
+
331
+ bootstrap_command_result
332
+ end
333
+
334
+ protected
335
+
336
+ # Default implementation -- override only if required by the transport
337
+ def wait_for_remote_response(wait_max_minutes)
338
+ end
339
+
340
+ def bootstrap_command
341
+ @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}"
342
+ end
343
+
344
+ def bootstrap_render_banner_command(chunk_num)
345
+ "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}"
346
+ end
347
+
348
+ def escape_windows_batch_characters(line)
349
+ # TODO: The commands are going to get redirected - do we need to escape &?
350
+ line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"}
351
+ end
352
+
353
+ def create_bootstrap_bat_command()
354
+ chunk_num = 0
355
+ bootstrap_bat = ""
356
+ banner = bootstrap_render_banner_command(chunk_num += 1)
357
+ render_template(load_template(config[:bootstrap_template])).each_line do |line|
358
+ escape_windows_batch_characters(line)
359
+ # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can
360
+ # confidently prefix every actual command with &&.
361
+ # TODO: Why does ^\n&& work directly through the commandline but not through SOAP?
362
+ render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})"
363
+ # Windows commands are limited to 8191 characters for machines running XP or higher but
364
+ # this includes the length of environment variables after they have been expanded.
365
+ # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner
366
+ # and once in every command redirection), we simply guess and set the max to 5000.
367
+ # TODO: When a more accurate method is available, fix this.
368
+ if bootstrap_bat.length + render_line.length + banner.length > 5000
369
+ # Can't fit it into this chunk? - flush (if necessary) and then try.
370
+ # Do this first because banner.length might change (e.g. due to an extra digit) and
371
+ # prevent a fit.
372
+ unless bootstrap_bat.empty?
373
+ yield banner + bootstrap_bat
374
+ bootstrap_bat = ""
375
+ banner = bootstrap_render_banner_command(chunk_num += 1)
376
+ end
377
+ # Will this ever fit?
378
+ if render_line.length + banner.length > 5000
379
+ raise "Command in bootstrap template too long by #{render_line.length + banner.length - 5000} characters : #{line}"
380
+ end
381
+ end
382
+ bootstrap_bat << render_line
383
+ end
384
+ raise "Bootstrap template was empty! Check #{config[:bootstrap_template]}" if bootstrap_bat.empty?
385
+ yield banner + bootstrap_bat
386
+ end
387
+
388
+ def bootstrap_bat_file
389
+ @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\""
390
+ end
391
+
392
+ def warn_chef_config_secret_key
393
+ ui.info "* " * 40
394
+ ui.warn(<<-WARNING)
395
+ \nSpecifying the encrypted data bag secret key using an 'encrypted_data_bag_secret'
396
+ entry in 'knife.rb' is deprecated. Please use the '--secret' or '--secret-file'
397
+ options of this command instead.
398
+
399
+ #{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this
400
+ behavior will be removed and any 'encrypted_data_bag_secret' entries in
401
+ 'knife.rb' will be ignored completely.
402
+ WARNING
403
+ ui.info "* " * 40
404
+ end
405
+ end
406
+ end
407
+ end