knife-windows 0.8.6 → 1.0.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +17 -3
  4. data/CHANGELOG.md +25 -6
  5. data/DOC_CHANGES.md +323 -0
  6. data/Gemfile +2 -1
  7. data/README.md +160 -29
  8. data/RELEASE_NOTES.md +59 -6
  9. data/appveyor.yml +42 -0
  10. data/ci.gemfile +15 -0
  11. data/knife-windows.gemspec +4 -2
  12. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +35 -21
  13. data/lib/chef/knife/bootstrap_windows_base.rb +155 -31
  14. data/lib/chef/knife/bootstrap_windows_ssh.rb +1 -1
  15. data/lib/chef/knife/bootstrap_windows_winrm.rb +17 -10
  16. data/lib/chef/knife/core/windows_bootstrap_context.rb +67 -16
  17. data/lib/chef/knife/windows_cert_generate.rb +155 -0
  18. data/lib/chef/knife/windows_cert_install.rb +62 -0
  19. data/lib/chef/knife/windows_helper.rb +3 -1
  20. data/lib/chef/knife/windows_listener_create.rb +100 -0
  21. data/lib/chef/knife/winrm.rb +84 -208
  22. data/lib/chef/knife/winrm_base.rb +36 -10
  23. data/lib/chef/knife/winrm_knife_base.rb +201 -0
  24. data/lib/chef/knife/winrm_session.rb +72 -0
  25. data/lib/chef/knife/winrm_shared_options.rb +47 -0
  26. data/lib/chef/knife/wsman_endpoint.rb +44 -0
  27. data/lib/chef/knife/wsman_test.rb +96 -0
  28. data/lib/knife-windows/path_helper.rb +77 -0
  29. data/lib/knife-windows/version.rb +1 -1
  30. data/spec/functional/bootstrap_download_spec.rb +41 -23
  31. data/spec/spec_helper.rb +11 -1
  32. data/spec/unit/knife/bootstrap_template_spec.rb +27 -27
  33. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +67 -23
  34. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +47 -0
  35. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -0
  36. data/spec/unit/knife/windows_cert_install_spec.rb +35 -0
  37. data/spec/unit/knife/windows_listener_create_spec.rb +61 -0
  38. data/spec/unit/knife/winrm_session_spec.rb +47 -0
  39. data/spec/unit/knife/winrm_spec.rb +222 -56
  40. data/spec/unit/knife/wsman_test_spec.rb +176 -0
  41. metadata +51 -20
data/RELEASE_NOTES.md CHANGED
@@ -6,14 +6,67 @@ Example Note:
6
6
  ## Example Heading
7
7
  Details about the thing that changed that needs to get included in the Release Notes in markdown.
8
8
  -->
9
- # knife-windows 0.8.6 release notes
10
- This release of knife-windows updates winrm-s to version 0.3.0 and em-winrm to version 0.7.
9
+ # knife-windows 1.0.0.rc.0 release notes:
10
+ This release of knife-windows includes new features to improve authentication,
11
+ simplify use of the WinRM SSL transport, and addresses compatibility issues with Chef Client 12.0.
11
12
 
12
- ## Features added in knife-windows 0.8.6
13
- None.
13
+ You can install the new features using the `gem` command:
14
14
 
15
- ## Issues fixed in knife-windows 0.8.6
16
- [knife-windows #218](https://github.com/chef/knife-windows/issues/218) winrm bootstrap with --winrm-authentication-protocol negotiate gives HTTP 400
15
+ gem install knife-windows --pre
16
+
17
+ ## Reporting issues and contributing
18
+
19
+ `knife-windows` issues like those addressed in this release should be reported in the ticketing system at https://github.com/chef/knife-windows/issues. You can learn more about how to contribute features and bug fixes to `knife-windows` in the [Chef Contributions document](http://docs.chef.io/community_contributions.html).
20
+
21
+ ## Breaking changes
22
+
23
+ ### Negotiate as the default authentication protocol
24
+ With this release, the default authentication protocol for WinRM
25
+ communication is negotiate, which is the same as that for tools built-in to
26
+ the Windows operating system. Prior to this release, the protocol depended
27
+ on the format of the `--winrm-user` option -- the basic authentication
28
+ protocol would be assumed unless that option had the format `domain\user`.
29
+
30
+ To revert to the behavior of previous releases or otherwise force `knife-windows` to use a specific authentication protocol such as
31
+ basic, use the `--winrm-authentication-protocol` option.
32
+
33
+ ### Default WinRM port depends on the transport
34
+ The default port for WinRM communication is now **5986** when the SSL transport is used (the transport is
35
+ configured by the `winrm_transport` option), otherwise it is **5985**. In
36
+ previous releases, if the port was not specified, it was always 5985.
37
+
38
+ To override this behavior, explicitly specify the desired port using the
39
+ `winrm_port` (`-p`) option.
40
+
41
+ ### Kerberos Keytab short option is now -T
42
+ The short option flag for --keytab-file is now -T to fix a conflict with the --identity-file option.
43
+
44
+ ## Features added in knife-windows 1.0.0.rc.0
45
+ * New `--winrm-authentication-protocol` option for explicit control of WinRM authentication
46
+ * `knife windows cert generate` subcommand:
47
+ Generates a certificate and related public key file for use in configuring a WinRM listener and validating communication involving it.
48
+ * `knife windows cert install` subcommand:
49
+ Installs a certificate such as one generated by the `cert generate`
50
+ subcommand into the Windows certificate store's LocalMachine personal store
51
+ so that it can be used as part of the configuration for a WinRM SSL listener
52
+ * `knife windows listener create` subcommand:
53
+ Creates a WinRM SSL listener on a Windows system
54
+ * Added `--hint` option for creating Ohai hints on bootstrap
55
+ * Validatorless bootstrapping is now supported
56
+ * New `--install-as-service` option will have Chef Client be installed as a service on bootstrap
57
+ * Added `--msi_url` option for providing an alternate URL to the Chef Client installation package
58
+ * `knife wsman test` subcommaned:
59
+ Verifies winrm functionality on a remote system, e.g. `knife wsman test 192.168.1.10 -m --winrm-transport ssl`
60
+
61
+ ## Issues fixed in knife-windows 1.0.0.rc.0
62
+ * [knife-windows #159](https://github.com/chef/knife-windows/issues/159) `winrm_port` option should default to 5986 if `winrm_transport` option is `ssl`
63
+ * [knife-windows #139](https://github.com/chef/knife-windows/issues/139) Force dev dependency on Chef 11 for test scenarios to avoid Ohai 8 conflict on Ruby 1.9.x
64
+ * [knife-windows #133](https://github.com/chef/knife-windows/issues/133) Bootstrap failure -- unable to validate SSL chef server endpoints
65
+ * [knife-windows #125](https://github.com/chef/knife-windows/issues/125) knife-windows should use PowerShell first before cscript to download the Chef Client msi
66
+ * [knife-windows #92](https://github.com/chef/knife-windows/issues/92) EventMachine issue: knife bootstrap windows winrm error
67
+ * [knife-windows #94](https://github.com/chef/knife-windows/issues/94) Remove Eventmachine dependency
68
+ * [knife-windows #213](https://github.com/chef/knife-windows/pull/213) Search possibilities of HOME for bootstrap templates
69
+ * [knife-windows #227](https://github.com/chef/knife-windows/issues/227) Exception: NoMethodError: undefined method 'gsub' for false:FalseClass
17
70
 
18
71
  ## knife-windows on RubyGems and Github
19
72
  https://rubygems.org/gems/knife-windows
data/appveyor.yml ADDED
@@ -0,0 +1,42 @@
1
+ version: "master-{build}"
2
+
3
+ os: Windows Server 2012
4
+ platform:
5
+ - x64
6
+
7
+ environment:
8
+ bundle_gemfile: ci.gemfile
9
+
10
+ matrix:
11
+ - ruby_version: "193"
12
+ chef_version: "< 12"
13
+
14
+ - ruby_version: "200"
15
+ chef_version: "< 12"
16
+
17
+ - ruby_version: "200"
18
+ chef_version: "~> 12.0"
19
+
20
+ - ruby_version: "200"
21
+ chef_version: "master"
22
+
23
+ clone_folder: c:\projects\knife-windows
24
+ clone_depth: 1
25
+ branches:
26
+ only:
27
+ - master
28
+
29
+ install:
30
+ - winrm quickconfig -q
31
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
32
+ - echo %PATH%
33
+ - ruby --version
34
+ - gem --version
35
+ - gem install bundler --quiet --no-ri --no-rdoc
36
+ - bundler --version
37
+
38
+ build_script:
39
+ - bundle install || bundle install || bundle install
40
+
41
+ test_script:
42
+ - bundle exec rake spec
data/ci.gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in knife-windows.gemspec
4
+ gemspec
5
+
6
+ if ENV['CHEF_VERSION'] == 'master'
7
+ gem 'chef', github: 'chef/chef'
8
+ else
9
+ gem 'chef', ENV['CHEF_VERSION']
10
+ end
11
+
12
+ gem "rspec", '~> 3.0'
13
+ gem "ruby-wmi"
14
+ gem "httpclient"
15
+ gem 'rake'
@@ -14,8 +14,9 @@ Gem::Specification.new do |s|
14
14
  s.description = s.summary
15
15
 
16
16
  s.required_ruby_version = ">= 1.9.1"
17
- s.add_dependency "winrm-s", "~> 0.3.1"
18
- s.add_dependency "em-winrm", "~> 0.7"
17
+ s.add_dependency "winrm", "~> 1.3"
18
+ s.add_dependency "winrm-s", "~> 0.3.0.dev.0"
19
+ s.add_dependency "nokogiri"
19
20
 
20
21
  s.add_development_dependency 'pry'
21
22
 
@@ -24,3 +25,4 @@ Gem::Specification.new do |s|
24
25
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
26
  s.require_paths = ["lib"]
26
27
  end
28
+
@@ -107,12 +107,8 @@ goto install
107
107
  :install
108
108
  @rem Install Chef using chef-client MSI installer
109
109
 
110
- <% url="https://www.opscode.com/chef/download?p=windows&pv=%MACHINE_OS%&m=%MACHINE_ARCH%" -%>
111
- <% url += latest_current_windows_chef_version_query -%>
112
- @set "REMOTE_SOURCE_MSI_URL=<%= url %>"
113
110
  @set "LOCAL_DESTINATION_MSI_PATH=<%= local_download_path %>"
114
111
  @set "CHEF_CLIENT_MSI_LOG_PATH=%TEMP%\chef-client-msi%RANDOM%.log"
115
- @set "FALLBACK_QUERY_STRING=&DownloadContext=PowerShell"
116
112
 
117
113
  @rem Clear any pre-existing downloads
118
114
  @echo Checking for existing downloaded package at "%LOCAL_DESTINATION_MSI_PATH%"
@@ -129,15 +125,16 @@ goto install
129
125
  @rem If there is somehow a name collision, remove pre-existing log
130
126
  @if EXIST "%CHEF_CLIENT_MSI_LOG_PATH%" del /f /q "%CHEF_CLIENT_MSI_LOG_PATH%"
131
127
 
132
- @echo Attempting to download client package using cscript...
133
- cscript /nologo <%= bootstrap_directory %>\wget.vbs /url:"%REMOTE_SOURCE_MSI_URL%" /path:"%LOCAL_DESTINATION_MSI_PATH%"
128
+ @echo Attempting to download client package using PowerShell if available...
129
+ @set "REMOTE_SOURCE_MSI_URL=<%= msi_url('%MACHINE_OS%', '%MACHINE_ARCH%', 'PowerShell') %>"
130
+ @set powershell_download=powershell.exe -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -File <%= bootstrap_directory %>\wget.ps1 "%REMOTE_SOURCE_MSI_URL%" "%LOCAL_DESTINATION_MSI_PATH%"
131
+ @echo !powershell_download!
132
+ @call !powershell_download!
134
133
 
135
- @rem Work around issues found in Windows Server 2012 around job objects not respecting WSMAN memory quotas
136
- @rem that cause the MSI download process to exceed the quota even when it is increased by administrators.
137
- @rem Retry the download using a more memory-efficient mechanism that only works if PowerShell is available.
138
134
  @set DOWNLOAD_ERROR_STATUS=!ERRORLEVEL!
135
+
139
136
  @if ERRORLEVEL 1 (
140
- @echo Failed cscript download with status code !DOWNLOAD_ERROR_STATUS! > "&2"
137
+ @echo Failed PowerShell download with status code !DOWNLOAD_ERROR_STATUS! > "&2"
141
138
  @if !DOWNLOAD_ERROR_STATUS!==0 set DOWNLOAD_ERROR_STATUS=2
142
139
  ) else (
143
140
  @rem Sometimes the error level is not set even when the download failed,
@@ -146,21 +143,29 @@ cscript /nologo <%= bootstrap_directory %>\wget.vbs /url:"%REMOTE_SOURCE_MSI_URL
146
143
  echo Failed download: download completed, but downloaded file not found > "&2"
147
144
  set DOWNLOAD_ERROR_STATUS=2
148
145
  ) else (
149
- echo Download via cscript succeeded.
146
+ echo Download via PowerShell succeeded.
150
147
  )
151
148
  )
152
149
 
153
150
  @if NOT %DOWNLOAD_ERROR_STATUS%==0 (
154
151
  @echo Warning: Failed to download "%REMOTE_SOURCE_MSI_URL%" to "%LOCAL_DESTINATION_MSI_PATH%"
155
- @echo Warning: Retrying download with PowerShell if available...
152
+ @echo Warning: Retrying download with cscript ...
156
153
 
157
154
  @if EXIST "%LOCAL_DESTINATION_MSI_PATH%" del /f /q "%LOCAL_DESTINATION_MSI_PATH%"
158
155
 
159
- @set powershell_download=powershell.exe -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -File <%= bootstrap_directory %>\wget.ps1 "%REMOTE_SOURCE_MSI_URL%%FALLBACK_QUERY_STRING%" "%LOCAL_DESTINATION_MSI_PATH%"
160
- @echo !powershell_download!
161
- @call !powershell_download!
156
+ @set "REMOTE_SOURCE_MSI_URL=<%= msi_url('%MACHINE_OS%', '%MACHINE_ARCH%') %>"
157
+ cscript /nologo <%= bootstrap_directory %>\wget.vbs /url:"%REMOTE_SOURCE_MSI_URL%" /path:"%LOCAL_DESTINATION_MSI_PATH%"
158
+
162
159
  @if NOT ERRORLEVEL 1 (
163
- echo Download via PowerShell succeeded.
160
+ @rem Sometimes the error level is not set even when the download failed,
161
+ @rem so check for the file to be sure it is there.
162
+ @if NOT EXIST "%LOCAL_DESTINATION_MSI_PATH%" (
163
+ echo Failed download: download completed, but downloaded file not found > "&2"
164
+ echo Exiting without bootstrapping due to download failure. > "&2"
165
+ exit /b 1
166
+ ) else (
167
+ echo Download via cscript succeeded.
168
+ )
164
169
  ) else (
165
170
  echo Failed to download "%REMOTE_SOURCE_MSI_URL%" with status code !ERRORLEVEL!. > "&2"
166
171
  echo Exiting without bootstrapping due to download failure. > "&2"
@@ -183,24 +188,33 @@ cscript /nologo <%= bootstrap_directory %>\wget.vbs /url:"%REMOTE_SOURCE_MSI_URL
183
188
  @endlocal
184
189
 
185
190
  @echo off
191
+
192
+ <% if client_pem -%>
193
+ > <%= bootstrap_directory %>\client.pem (
194
+ <%= escape_and_echo(::File.read(::File.expand_path(client_pem))) %>
195
+ )
196
+ <% end -%>
197
+
186
198
  echo Writing validation key...
187
199
 
200
+ <% if validation_key -%>
188
201
  > <%= bootstrap_directory %>\validation.pem (
189
- <%= validation_key %>
202
+ <%= escape_and_echo(validation_key) %>
190
203
  )
204
+ <% end -%>
191
205
 
192
206
  echo Validation key written.
193
207
  @echo on
194
208
 
195
- <% if @config[:encrypted_data_bag_secret] -%>
209
+ <% if @config[:secret] -%>
196
210
  > <%= bootstrap_directory %>\encrypted_data_bag_secret (
197
- <%= encrypted_data_bag_secret %>
211
+ <%= secret %>
198
212
  )
199
213
  <% end -%>
200
214
 
201
- <% unless trusted_certs.empty? -%>
215
+ <% unless trusted_certs_script.empty? -%>
202
216
  mkdir <%= bootstrap_directory %>\trusted_certs
203
- <%= trusted_certs %>
217
+ <%= trusted_certs_script %>
204
218
  <% end -%>
205
219
 
206
220
  <%# Generate Ohai Hints -%>
@@ -20,6 +20,8 @@ require 'chef/knife'
20
20
  require 'chef/knife/bootstrap'
21
21
  require 'chef/encrypted_data_bag_item'
22
22
  require 'chef/knife/core/windows_bootstrap_context'
23
+ # Chef 11 PathHelper doesn't have #home
24
+ #require 'chef/util/path_helper'
23
25
 
24
26
  class Chef
25
27
  class Knife
@@ -60,16 +62,28 @@ class Chef
60
62
  :description => "Avoid a proxy server for the given addresses",
61
63
  :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np }
62
64
 
65
+ # DEPR: Remove this option in Chef 13
63
66
  option :distro,
64
67
  :short => "-d DISTRO",
65
68
  :long => "--distro DISTRO",
66
- :description => "Bootstrap a distro using a template",
67
- :default => "windows-chef-client-msi"
69
+ :description => "Bootstrap a distro using a template. [DEPRECATED] Use --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
+ :long => "--bootstrap-template TEMPLATE",
77
+ :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."
68
78
 
79
+ # DEPR: Remove this option in Chef 13
69
80
  option :template_file,
70
81
  :long => "--template-file TEMPLATE",
71
- :description => "Full path to location of template to use",
72
- :default => false
82
+ :description => "Full path to location of template to use. [DEPRECATED] Use --bootstrap-template option instead.",
83
+ :proc => Proc.new { |v|
84
+ Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use --bootstrap-template option instead.")
85
+ v
86
+ }
73
87
 
74
88
  option :run_list,
75
89
  :short => "-r RUN_LIST",
@@ -78,6 +92,15 @@ class Chef
78
92
  :proc => lambda { |o| o.split(",") },
79
93
  :default => []
80
94
 
95
+ option :hint,
96
+ :long => "--hint HINT_NAME[=HINT_FILE]",
97
+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
98
+ :proc => Proc.new { |h|
99
+ Chef::Config[:knife][:hints] ||= Hash.new
100
+ name, path = h.split("=")
101
+ Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new
102
+ }
103
+
81
104
  option :first_boot_attributes,
82
105
  :short => "-j JSON_ATTRIBS",
83
106
  :long => "--json-attributes",
@@ -85,12 +108,14 @@ class Chef
85
108
  :proc => lambda { |o| JSON.parse(o) },
86
109
  :default => {}
87
110
 
111
+ # Mismatch between option 'encrypted_data_bag_secret' and it's long value '--secret' is by design for compatibility
88
112
  option :encrypted_data_bag_secret,
89
113
  :short => "-s SECRET",
90
114
  :long => "--secret ",
91
115
  :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.",
92
116
  :default => false
93
117
 
118
+ # Mismatch between option 'encrypted_data_bag_secret_file' and it's long value '--secret-file' is by design for compatibility
94
119
  option :encrypted_data_bag_secret_file,
95
120
  :long => "--secret-file SECRET_FILE",
96
121
  :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."
@@ -114,23 +139,51 @@ class Chef
114
139
  :long => "--[no-]node-verify-api-cert",
115
140
  :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
116
141
  :boolean => true
142
+
143
+ option :msi_url,
144
+ :short => "-u URL",
145
+ :long => "--msi_url URL",
146
+ :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.",
147
+ :default => ''
148
+
149
+ option :install_as_service,
150
+ :long => "--install-as-service",
151
+ :description => "Install chef-client as service in windows machine",
152
+ :default => false
117
153
  end
118
154
  end
119
155
 
120
- # TODO: This should go away when CHEF-2193 is fixed
156
+ def default_bootstrap_template
157
+ "windows-chef-client-msi"
158
+ end
159
+
160
+ def bootstrap_template
161
+ # The order here is important. We want to check if we have the new Chef 12 option is set first.
162
+ # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at
163
+ # the end.
164
+ config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template
165
+ end
166
+
167
+ # TODO: This should go away when CHEF-2193 is fixed
121
168
  def load_template(template=nil)
122
169
  # Are we bootstrapping using an already shipped template?
123
- if config[:template_file]
124
- bootstrap_files = config[:template_file]
125
- else
126
- bootstrap_files = []
127
- bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap', "#{config[:distro]}.erb")
128
- bootstrap_files << File.join(Dir.pwd, ".chef", "bootstrap", "#{config[:distro]}.erb")
129
- bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{config[:distro]}.erb")
130
- bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{config[:distro]}.erb"))
131
- bootstrap_files.flatten!
170
+
171
+ template = bootstrap_template
172
+
173
+ # Use the template directly if it's a path to an actual file
174
+ if File.exists?(template)
175
+ Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
176
+ return IO.read(template).chomp
132
177
  end
133
178
 
179
+ # Otherwise search the template directories until we find the right one
180
+ bootstrap_files = []
181
+ bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb")
182
+ bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
183
+ ::Knife::Windows::PathHelper.all_homes('.chef', 'bootstrap', "#{template}.erb") { |p| bootstrap_files << p }
184
+ bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb"))
185
+ bootstrap_files.flatten!
186
+
134
187
  template = Array(bootstrap_files).find do |bootstrap_template|
135
188
  Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
136
189
  ::File.exists?(bootstrap_template)
@@ -146,15 +199,24 @@ class Chef
146
199
  IO.read(template).chomp
147
200
  end
148
201
 
202
+ def bootstrap_context
203
+ @bootstrap_context ||= Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config)
204
+ end
205
+
149
206
  def render_template(template=nil)
150
- if config[:encrypted_data_bag_secret_file]
151
- config[:encrypted_data_bag_secret] = Chef::EncryptedDataBagItem.load_secret(config[:encrypted_data_bag_secret_file])
207
+ if config[:secret_file]
208
+ config[:secret] = Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
152
209
  end
153
- context = Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config)
154
- Erubis::Eruby.new(template).evaluate(context)
210
+ Erubis::Eruby.new(template).evaluate(bootstrap_context)
155
211
  end
156
212
 
157
213
  def bootstrap(proto=nil)
214
+ if Chef::Config[:knife][:encrypted_data_bag_secret_file] || Chef::Config[:knife][:encrypted_data_bag_secret]
215
+ warn_chef_config_secret_key
216
+ config[:secret_file] ||= Chef::Config[:knife][:encrypted_data_bag_secret_file]
217
+ config[:secret] ||= Chef::Config[:knife][:encrypted_data_bag_secret]
218
+ end
219
+
158
220
  validate_name_args!
159
221
 
160
222
  @node_name = Array(@name_args).first
@@ -163,13 +225,32 @@ class Chef
163
225
 
164
226
  STDOUT.sync = STDERR.sync = true
165
227
 
228
+ if (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
229
+ if Chef::VERSION.split('.').first.to_i == 11
230
+ ui.error("Unable to find validation key. Please verify your configuration file for validation_key config value.")
231
+ exit 1
232
+ end
233
+
234
+ unless locate_config_value(:chef_node_name)
235
+ ui.error("You must pass a node name with -N when bootstrapping with user credentials")
236
+ exit 1
237
+ end
238
+
239
+ client_builder.run
240
+ bootstrap_context.client_pem = client_builder.client_path
241
+ else
242
+ ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...")
243
+ ui.info("Delete your validation key in order to use your user credentials instead")
244
+ ui.info("")
245
+ end
246
+
166
247
  wait_for_remote_response( config[:auth_timeout].to_i )
167
248
  ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}")
168
249
  # create a bootstrap.bat file on the node
169
250
  # we have to run the remote commands in 2047 char chunks
170
- create_bootstrap_bat_command do |command_chunk, chunk_num|
251
+ create_bootstrap_bat_command do |command_chunk|
171
252
  begin
172
- render_command_result = run_command(%Q!cmd.exe /C echo "Rendering #{bootstrap_bat_file} chunk #{chunk_num}" && #{command_chunk}!)
253
+ render_command_result = run_command(command_chunk)
173
254
  ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0
174
255
  render_command_result
175
256
  rescue SystemExit => e
@@ -180,6 +261,7 @@ class Chef
180
261
  # execute the bootstrap.bat file
181
262
  bootstrap_command_result = run_command(bootstrap_command)
182
263
  ui.error("Bootstrap command returned #{bootstrap_command_result}") if bootstrap_command_result != 0
264
+
183
265
  bootstrap_command_result
184
266
  end
185
267
 
@@ -193,20 +275,48 @@ class Chef
193
275
  @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}"
194
276
  end
195
277
 
196
- def create_bootstrap_bat_command(&block)
197
- bootstrap_bat = []
278
+ def bootstrap_render_banner_command(chunk_num)
279
+ "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}"
280
+ end
281
+
282
+ def escape_windows_batch_characters(line)
283
+ # TODO: The commands are going to get redirected - do we need to escape &?
284
+ line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"}
285
+ end
286
+
287
+ def create_bootstrap_bat_command()
198
288
  chunk_num = 0
289
+ bootstrap_bat = ""
290
+ banner = bootstrap_render_banner_command(chunk_num += 1)
199
291
  render_template(load_template(config[:bootstrap_template])).each_line do |line|
200
- # escape WIN BATCH special chars
201
- line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"}
202
- # windows commands are limited to 2047 characters
203
- if((bootstrap_bat + [line]).join(" && ").size > 2047 )
204
- yield bootstrap_bat.join(" && "), chunk_num += 1
205
- bootstrap_bat = []
292
+ escape_windows_batch_characters(line)
293
+ # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can
294
+ # confidently prefix every actual command with &&.
295
+ # TODO: Why does ^\n&& work directly through the commandline but not through SOAP?
296
+ render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})"
297
+ # Windows commands are limited to 8191 characters for machines running XP or higher but
298
+ # this includes the length of environment variables after they have been expanded.
299
+ # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner
300
+ # and once in every command redirection), we simply guess and set the max to 5000.
301
+ # TODO: When a more accurate method is available, fix this.
302
+ if bootstrap_bat.length + render_line.length + banner.length > 5000
303
+ # Can't fit it into this chunk? - flush (if necessary) and then try.
304
+ # Do this first because banner.length might change (e.g. due to an extra digit) and
305
+ # prevent a fit.
306
+ unless bootstrap_bat.empty?
307
+ yield banner + bootstrap_bat
308
+ bootstrap_bat = ""
309
+ banner = bootstrap_render_banner_command(chunk_num += 1)
310
+ end
311
+ # Will this ever fit?
312
+ if render_line.length + banner.length > 5000
313
+ raise "Command in bootstrap template too long by #{render_line.length + banner.length - 5000} characters : #{line}"
314
+ end
206
315
  end
207
- bootstrap_bat << ">> #{bootstrap_bat_file} (echo.#{line.chomp.strip})"
316
+ bootstrap_bat << render_line
208
317
  end
209
- yield bootstrap_bat.join(" && "), chunk_num += 1
318
+ raise "Bootstrap template was empty! Check #{config[:bootstrap_template]}" if bootstrap_bat.empty?
319
+ yield banner + bootstrap_bat
210
320
  end
211
321
 
212
322
  def bootstrap_bat_file
@@ -215,7 +325,21 @@ class Chef
215
325
 
216
326
  def locate_config_value(key)
217
327
  key = key.to_sym
218
- Chef::Config[:knife][key] || config[key]
328
+ config[key] || Chef::Config[:knife][key]
329
+ end
330
+
331
+ def warn_chef_config_secret_key
332
+ ui.info "* " * 40
333
+ ui.warn(<<-WARNING)
334
+ \nSpecifying the encrypted data bag secret key using an 'encrypted_data_bag_secret'
335
+ entry in 'knife.rb' is deprecated. Please use the '--secret' or '--secret-file'
336
+ options of this command instead.
337
+
338
+ #{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this
339
+ behavior will be removed and any 'encrypted_data_bag_secret' entries in
340
+ 'knife.rb' will be ignored completely.
341
+ WARNING
342
+ ui.info "* " * 40
219
343
  end
220
344
  end
221
345
  end