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

Sign up to get free protection for your applications and to get access to all the features.
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