knife-windows 0.8.6 → 1.0.0.rc.0

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 (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