chef 18.3.0 → 18.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/chef.gemspec +3 -3
  4. data/lib/chef/chef_fs/file_pattern.rb +1 -1
  5. data/lib/chef/chef_fs/path_utils.rb +7 -9
  6. data/lib/chef/cookbook/synchronizer.rb +2 -3
  7. data/lib/chef/delayed_evaluator.rb +4 -0
  8. data/lib/chef/file_access_control/windows.rb +4 -1
  9. data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +2 -0
  10. data/lib/chef/mixin/homebrew_user.rb +6 -3
  11. data/lib/chef/monkey_patches/net-http.rb +5 -5
  12. data/lib/chef/node/attribute_collections.rb +2 -1
  13. data/lib/chef/node/immutable_collections.rb +2 -1
  14. data/lib/chef/node/mixin/state_tracking_array.rb +41 -0
  15. data/lib/chef/node.rb +22 -0
  16. data/lib/chef/provider/package/apt.rb +11 -2
  17. data/lib/chef/provider/package/chocolatey.rb +228 -24
  18. data/lib/chef/provider/package/zypper.rb +5 -0
  19. data/lib/chef/provider/powershell_script.rb +96 -6
  20. data/lib/chef/provider/service/systemd.rb +23 -8
  21. data/lib/chef/provider/service/windows.rb +1 -0
  22. data/lib/chef/provider/service.rb +14 -0
  23. data/lib/chef/provider/user.rb +5 -1
  24. data/lib/chef/recipe.rb +3 -11
  25. data/lib/chef/resource/_rest_resource.rb +1 -1
  26. data/lib/chef/resource/apt_package.rb +19 -0
  27. data/lib/chef/resource/apt_repository.rb +2 -1
  28. data/lib/chef/resource/chef_client_systemd_timer.rb +1 -1
  29. data/lib/chef/resource/chocolatey_installer.rb +207 -0
  30. data/lib/chef/resource/chocolatey_package.rb +8 -0
  31. data/lib/chef/resource/homebrew_package.rb +1 -1
  32. data/lib/chef/resource/locale.rb +5 -2
  33. data/lib/chef/resource/macos_pkg.rb +111 -0
  34. data/lib/chef/resource/powershell_script.rb +5 -1
  35. data/lib/chef/resource/service.rb +3 -0
  36. data/lib/chef/resource/sudo.rb +37 -2
  37. data/lib/chef/resource/support/ulimit.erb +40 -0
  38. data/lib/chef/resource/user_ulimit.rb +38 -0
  39. data/lib/chef/resources.rb +2 -0
  40. data/lib/chef/version.rb +1 -1
  41. data/spec/data/trusted_certs/example.crt +29 -20
  42. data/spec/data/trusted_certs/example_no_cn.crt +30 -34
  43. data/spec/functional/resource/chocolatey_package_spec.rb +2 -2
  44. data/spec/functional/resource/zypper_package_spec.rb +10 -0
  45. data/spec/unit/compliance/reporter/chef_server_automate_spec.rb +1 -1
  46. data/spec/unit/delayed_evaluator_spec.rb +35 -0
  47. data/spec/unit/node_spec.rb +6 -0
  48. data/spec/unit/provider/package/apt_spec.rb +18 -13
  49. data/spec/unit/provider/package/chocolatey_spec.rb +40 -12
  50. data/spec/unit/provider/powershell_script_spec.rb +100 -4
  51. data/spec/unit/provider/service/systemd_service_spec.rb +1 -0
  52. data/spec/unit/provider/user/linux_spec.rb +10 -0
  53. data/spec/unit/resource/chef_client_systemd_timer_spec.rb +1 -1
  54. data/spec/unit/resource/chocolatey_installer_spec.rb +151 -0
  55. data/spec/unit/resource/macos_pkg_spec.rb +38 -0
  56. data/spec/unit/resource/powershell_script_spec.rb +2 -2
  57. metadata +24 -12
@@ -14,10 +14,15 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
  #
17
+ # This uses class variables on purpose to maintain a state cache between resources,
18
+ # since they work on shared state
19
+ #
20
+ # rubocop:disable Style/ClassVars
17
21
 
18
22
  require_relative "../package"
19
23
  require_relative "../../resource/chocolatey_package"
20
24
  require_relative "../../win32/api/command_line_helper" if ChefUtils.windows?
25
+ require "zip" unless defined?(Zip)
21
26
 
22
27
  class Chef
23
28
  class Provider
@@ -32,12 +37,16 @@ class Chef
32
37
  PATHFINDING_POWERSHELL_COMMAND = "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')".freeze
33
38
  CHOCO_MISSING_MSG = <<~EOS.freeze
34
39
  Could not locate your Chocolatey install. To install chocolatey, we recommend
35
- the 'chocolatey' cookbook (https://github.com/chocolatey/chocolatey-cookbook).
40
+ the 'chocolatey_installer' resource.
36
41
  If Chocolatey is installed, ensure that the 'ChocolateyInstall' environment
37
42
  variable is correctly set. You can verify this with the PowerShell command
38
43
  '#{PATHFINDING_POWERSHELL_COMMAND}'.
39
44
  EOS
40
45
 
46
+ # initialize our cache on load
47
+ @@choco_available_packages = nil
48
+ @@choco_config = nil
49
+
41
50
  # Responsible for building the current_resource.
42
51
  #
43
52
  # @return [Chef::Resource::ChocolateyPackage] the current_resource
@@ -45,6 +54,10 @@ class Chef
45
54
  @current_resource = Chef::Resource::ChocolateyPackage.new(new_resource.name)
46
55
  current_resource.package_name(new_resource.package_name)
47
56
  current_resource.version(build_current_versions)
57
+ # Ensure that we have a working chocolatey executable - this used to be
58
+ # covered off by loading the resource, but since that's no longer required,
59
+ # we're going to put a quick check here to fail early!
60
+ choco_exe
48
61
  current_resource
49
62
  end
50
63
 
@@ -130,19 +143,48 @@ class Chef
130
143
  # install from, but like the rubygem provider's sources which are more like repos.
131
144
  def check_resource_semantics!; end
132
145
 
133
- def self.get_choco_version
134
- @get_choco_version ||= powershell_exec!("choco --version").result
146
+ def get_choco_version
147
+ @get_choco_version ||= powershell_exec!("#{choco_exe} --version").result
135
148
  end
136
149
 
137
150
  # Choco V2 uses 'Search' for remote repositories and 'List' for local packages
138
- def self.query_command
151
+ def query_command
139
152
  return "list" if get_choco_version.match?(/^1/)
140
153
 
141
154
  "search"
142
155
  end
143
156
 
144
- def query_command
145
- self.class.query_command
157
+ # invalidate cache for testing purposes
158
+ def invalidate_cache
159
+ @@choco_config = nil
160
+ end
161
+
162
+ # This checks that the repo list has not changed between now and when we last checked
163
+ # the cache
164
+ def cache_is_valid?
165
+ return false if @@choco_config.nil? || (actual_config != @@choco_config)
166
+
167
+ true
168
+ end
169
+
170
+ # walk the collection to find peer resources to ensure that
171
+ # we get as much of the cache this run as possible. Unified mode
172
+ # sub-resources will, of course, be incremental with this, since we can't
173
+ # grab them in advance, but even in that case we're still way, way
174
+ # more efficient with our queries...
175
+ #
176
+ # @return [Array] List of chocolatey packages referenced in the run list
177
+ def collect_package_requests(ignore_list: [])
178
+ return ["*"] if new_resource.bulk_query || Chef::Config[:always_use_bulk_chocolatey_package_list]
179
+
180
+ # Get to the root of the resource collection
181
+ rc = run_context.parent_run_context || run_context
182
+ rc = rc.parent_run_context while rc.parent_run_context
183
+
184
+ package_collection = package_name_array
185
+ package_collection += nested_package_resources(rc.resource_collection)
186
+ # downcase the array and uniq. sorted for easier testing...
187
+ package_collection.uniq.sort.filter { |pkg| !ignore_list.include?(pkg) }
146
188
  end
147
189
 
148
190
  private
@@ -158,6 +200,22 @@ class Chef
158
200
  gem_v1 <=> gem_v2
159
201
  end
160
202
 
203
+ # Cache the configuration in order to ensure that we can check our
204
+ # package cache is valid for a run
205
+ def actual_config
206
+ config_path = ::File.join("#{choco_install_path}", "config", "chocolatey.config")
207
+ if ::File.exist?(config_path)
208
+ return ::File.read(config_path)
209
+ end
210
+
211
+ nil
212
+ end
213
+
214
+ # update the validity of the package cache
215
+ def set_package_cache
216
+ @@choco_config = actual_config
217
+ end
218
+
161
219
  # Magic to find where chocolatey is installed in the system, and to
162
220
  # return the full path of choco.exe
163
221
  #
@@ -175,9 +233,15 @@ class Chef
175
233
 
176
234
  # lets us mock out an incorrect value for testing.
177
235
  def choco_install_path
178
- result = powershell_exec!(PATHFINDING_POWERSHELL_COMMAND).result
179
- result = "" if result.empty?
180
- result
236
+ @choco_install_path ||= begin
237
+ result = powershell_exec!(PATHFINDING_POWERSHELL_COMMAND).result
238
+ result = "" if result.empty?
239
+ result
240
+ end
241
+ end
242
+
243
+ def choco_lib_path
244
+ ::File.join(choco_install_path, "lib")
181
245
  end
182
246
 
183
247
  # Helper to dispatch a choco command through shell_out using the timeout
@@ -218,6 +282,17 @@ class Chef
218
282
  Hash[*lowercase_names(new_resource.package_name).zip(desired_versions).flatten]
219
283
  end
220
284
 
285
+ def nested_package_resources(res)
286
+ package_collection = []
287
+ res.each do |child_res|
288
+ package_collection += nested_package_resources(child_res.resources)
289
+ next unless child_res.is_a?(Chef::Resource::ChocolateyPackage)
290
+
291
+ package_collection += child_res.package_name.flatten
292
+ end
293
+ package_collection
294
+ end
295
+
221
296
  # Helper to construct optional args out of new_resource
222
297
  #
223
298
  # @param include_source [Boolean] should the source parameter be added
@@ -234,37 +309,127 @@ class Chef
234
309
  #
235
310
  # @return [Hash] name-to-version mapping of available packages
236
311
  def available_packages
237
- return @available_packages if @available_packages
312
+ return @available_packages unless @available_packages.nil?
313
+
314
+ # @available_packages is per object - each resource is an object, meaning if you
315
+ # have a LOT of chocolatey package installs, then this quickly gets very slow.
316
+ # So we use @@choco_available_packages instead - BUT it's important to ensure that
317
+ # the cache is valid before you do this. There are two cache items that can change:
318
+ # a) the sources - we check this with cache_is_valid?
319
+ if cache_is_valid? && @@choco_available_packages.is_a?(Hash) &&
320
+ @@choco_available_packages[new_resource.list_options]
321
+
322
+ # Ensure we have the package names, or else double check...
323
+ need_redo = false
324
+ package_name_array.each do |pkg|
325
+ need_redo = true unless @@choco_available_packages[new_resource.list_options][pkg.downcase]
326
+ end
327
+ return @@choco_available_packages[new_resource.list_options] unless need_redo
328
+ end
329
+ if new_resource.list_options
330
+ Chef::Log.info("Fetching chocolatey package list with options #{new_resource.list_options.inspect}")
331
+ else
332
+ Chef::Log.info("Fetching chocolatey package list")
333
+ end
238
334
 
239
- @available_packages = {}
240
- package_name_array.each do |pkg|
335
+ # Only reset the array if the cache is invalid - if we're just augmenting it, don't
336
+ # clear it
337
+ @@choco_available_packages = {} if @@choco_available_packages.nil? || !cache_is_valid?
338
+ if @@choco_available_packages[new_resource.list_options].nil?
339
+ @@choco_available_packages[new_resource.list_options] = {}
340
+ end
341
+
342
+ # Attempt to get everything we need in a single query.
343
+ # Grab 25 packages at a time at most, to avoid hurting servers too badly
344
+ #
345
+ # For v1 we actually used to grab the entire list of packages, but we found
346
+ # that it could cause undue load on really large package lists
347
+ collect_package_requests(
348
+ ignore_list: @@choco_available_packages[new_resource.list_options].keys
349
+ ).each_slice(25) do |pkg_set|
241
350
  available_versions =
242
351
  begin
243
- cmd = [ query_command, "-r", pkg ]
244
- cmd += common_options
245
- cmd.push( new_resource.list_options ) if new_resource.list_options
246
-
247
- raw = parse_list_output(*cmd)
248
- raw.keys.each_with_object({}) do |name, available|
249
- available[name] = desired_name_versions[name] || raw[name]
250
- end
352
+ cmd = [ query_command, "-r" ]
353
+
354
+ # Chocolatey doesn't actually take a wildcard for this query, however
355
+ # it will return all packages when using '*' as a query
356
+ unless pkg_set == ["*"]
357
+ cmd += pkg_set
251
358
  end
252
- @available_packages.merge! available_versions
359
+ cmd += common_options
360
+ cmd.push( new_resource.list_options ) if new_resource.list_options
361
+
362
+ Chef::Log.debug("Choco List Command: #{cmd}")
363
+
364
+ raw = parse_list_output(*cmd)
365
+ raw.keys.each_with_object({}) do |name, available|
366
+ available[name] = desired_name_versions[name] || raw[name]
367
+ end
368
+ end
369
+ @@choco_available_packages[new_resource.list_options].merge!(available_versions)
253
370
  end
254
- @available_packages
371
+ # Mark the cache as valid, with the required metadata
372
+ set_package_cache
373
+ # Why both? So when we fail to find a package once, we don't try on every
374
+ # retry, even though it would be reasonable to do so if queried in another
375
+ # resource (because the chocolatey configuration may well have changed!)
376
+ @available_packages = @@choco_available_packages[new_resource.list_options]
255
377
  end
256
378
 
257
379
  # Installed packages in chocolatey as a Hash of names mapped to versions
258
- # (names are downcased for case-insensitive matching)
380
+ # (names are downcased for case-insensitive matching). Depending on the user
381
+ # preference, we get these either from the local database, or from the choco
382
+ # list command
259
383
  #
384
+ # @return [Hash] name-to-version mapping of installed packages
385
+ def installed_packages
386
+ if new_resource.use_choco_list == false || !Chef::Config[:always_use_choco_list]
387
+ installed_packages_via_choco
388
+ else
389
+ installed_packages_via_disk
390
+ end
391
+ end
392
+
260
393
  # Beginning with Choco 2.0, "list" returns local packages only while "search" returns packages from external package sources
261
394
  #
262
395
  # @return [Hash] name-to-version mapping of installed packages
263
- def installed_packages
396
+ def installed_packages_via_choco
264
397
  @installed_packages ||= Hash[*parse_list_output("list", "-l", "-r").flatten]
265
398
  @installed_packages
266
399
  end
267
400
 
401
+ # Return packages sourced from the local disk - because this doesn't have
402
+ # shell out overhead, this ends up being a significant performance win
403
+ # vs calling choco list
404
+ #
405
+ # @return [Hash] name-to-version mapping of installed packages
406
+ def installed_packages_via_disk
407
+ @installed_packages ||= begin
408
+ targets = new_resource.name
409
+ target_dirs = []
410
+ # If we're using a single package name, have it at the head of the list
411
+ # so we can get more performance. In either case, the
412
+ # array is filled by the call to `get_local_pkg_dirs` below - but
413
+ # that contains all possible package folders, and so we push our
414
+ # guess to the front as an optimization.
415
+ target_dirs << targets.first.downcase if targets.length == 1
416
+ if targets.downcase is_a?(String)
417
+ target_dirs << targets
418
+ end
419
+ target_dirs += get_local_pkg_dirs(choco_lib_path)
420
+ fetch_package_versions(choco_lib_path, target_dirs, targets)
421
+ end
422
+ end
423
+
424
+ # Grab the nupkg folder list
425
+ def get_local_pkg_dirs(base_dir)
426
+ return [] unless Dir.exist?(base_dir)
427
+
428
+ Dir.entries(base_dir).select do |dir|
429
+ ::File.directory?(::File.join(base_dir, dir)) && !dir.start_with?(".")
430
+ end
431
+ end
432
+
268
433
  # Helper to convert choco.exe list output to a Hash
269
434
  # (names are downcased for case-insensitive matching)
270
435
  #
@@ -296,7 +461,46 @@ class Chef
296
461
  args.push( [ "--password", new_resource.password ]) if new_resource.password
297
462
  args
298
463
  end
464
+
465
+ # Fetch the local package versions from chocolatey
466
+ def fetch_package_versions(base_dir, target_dirs, targets)
467
+ pkg_versions = {}
468
+ target_dirs.each do |dir|
469
+ pkg_versions.merge!(get_pkg_data(::File.join(base_dir, dir)))
470
+ # return early if we found the single package version we were looking for
471
+ return pkg_versions if targets.length == 1 && pkg_versions[targets.first]
472
+ end
473
+ pkg_versions
474
+ end
475
+
476
+ # Grab the locally installed packages from the nupkg list
477
+ # rather than shelling out to chocolatey
478
+ def get_pkg_data(path)
479
+ t = ::File.join(path, "*.nupkg").gsub("\\", "/")
480
+ targets = Dir.glob(t)
481
+
482
+ # Extract package version from the first nuspec file in this nupkg
483
+ targets.each do |target|
484
+ Zip::File.open(target) do |zip_file|
485
+ zip_file.each do |entry|
486
+ next unless entry.name.end_with?(".nuspec")
487
+
488
+ f = entry.get_input_stream
489
+ doc = REXML::Document.new(f.read.to_s)
490
+ f.close
491
+ id = doc.elements["package/metadata/id"]
492
+ version = doc.elements["package/metadata/version"]
493
+ return { id.text.to_s.downcase => version.text } if id && version
494
+ end
495
+ end
496
+ end
497
+ {}
498
+ rescue StandardError => e
499
+ Chef::Log.warn("Failed to get package info for #{path}: #{e}")
500
+ {}
501
+ end
299
502
  end
300
503
  end
301
504
  end
302
505
  end
506
+ # rubocop:enable Style/ClassVars
@@ -132,6 +132,11 @@ class Chef
132
132
  end
133
133
  current_version ||= latest_version if is_installed
134
134
  current_version
135
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
136
+ # zypper returns a '104' code if info is called for a non-existent package
137
+ return nil if e.message =~ /'104'/
138
+
139
+ raise
135
140
  end
136
141
 
137
142
  def resolve_available_version(package_name, new_version)
@@ -27,8 +27,13 @@ class Chef
27
27
  provides :powershell_script
28
28
 
29
29
  action :run do
30
- validate_script_syntax!
31
- super()
30
+ Chef::Log.debug("using inline impl: #{new_resource.use_inline_powershell}")
31
+ if new_resource.use_inline_powershell
32
+ run_using_powershell_exec
33
+ else
34
+ validate_script_syntax!
35
+ super()
36
+ end
32
37
  end
33
38
 
34
39
  # Set InputFormat to None as PowerShell will hang if STDIN is redirected
@@ -38,6 +43,7 @@ class Chef
38
43
  def command
39
44
  # Must use -File rather than -Command to launch the script
40
45
  # file created by the base class that contains the script
46
+
41
47
  # code -- otherwise, powershell.exe does not propagate the
42
48
  # error status of a failed Windows process that ran at the
43
49
  # end of the script, it gets changed to '1'.
@@ -52,6 +58,69 @@ class Chef
52
58
 
53
59
  protected
54
60
 
61
+ # Run the inline version of powershell, using powershell_exec rather than powershell_out - this should
62
+ # hopefully
63
+ def run_using_powershell_exec
64
+ # Because of the nature of powershell_exec, we can't easily stream data to the shell, so we just flat out
65
+ # disallow this combination
66
+ if new_resource.live_stream
67
+ raise "powershell_script does not support live_stream when inline powershell is enabled - please choose one or the other"
68
+ end
69
+
70
+ # This comes from the execute super resource - since we want to limit the scope of this to powershell,
71
+ # we use this here
72
+ if creates && sentinel_file.exist?
73
+ logger.debug("#{new_resource} sentinel file #{sentinel_file} exists - nothing to do")
74
+ return false
75
+ end
76
+
77
+ converge_by("execute direct powershell #{new_resource.name}") do
78
+ # For scoping - otherwise, we lose return_code/stdout/stderr when the timeout block is over
79
+ return_code = nil
80
+ stdout = nil
81
+ stderr = nil
82
+
83
+ r = powershell_exec!(powershell_wrapper_script, new_resource.interpreter.to_sym, timeout: new_resource.timeout)
84
+ # Split out the stdout/return code format if needed
85
+ return_code = r.result
86
+
87
+ # The script returns an array if there is stdout, or just a plain return value if
88
+ # there is not - we need to handle both of these cases for full coverage
89
+ if return_code.is_a?(Array)
90
+ # Why to_s? Because if the only powershell output is an object,
91
+ # it'll be returned here
92
+ stdout = return_code[0].to_s
93
+ stderr = r.errors.join("\n")
94
+ return_code = return_code[-1]
95
+ else
96
+ stdout = ""
97
+ stderr = r.errors.join("\n")
98
+ end
99
+
100
+ Chef::Log.info("Powershell output: #{stdout}") unless stdout.empty?
101
+ Chef::Log.info("Powershell error: #{stderr}") unless stderr.empty?
102
+
103
+ # Check the return code, and validate. This code is cribbed from execute.rb, and does exactly the same
104
+ # thing as it does there
105
+ valid_returns = new_resource.returns
106
+ valid_returns = [valid_returns] if valid_returns.is_a?(Integer)
107
+ unless valid_returns.include?(return_code)
108
+ # Handle sensitive results
109
+ if sensitive?
110
+ ex = ChefPowerShell::PowerShellExceptions::PowerShellCommandFailed.new("Command execution failed. STDOUT/STDERR suppressed for sensitive resource")
111
+ # Forcibly hide the exception cause chain here so we don't log the unredacted version
112
+ def ex.cause
113
+ nil
114
+ end
115
+ raise ex
116
+ else
117
+ raise ChefPowerShell::PowerShellExceptions::PowerShellCommandFailed.new("Powershell command returned #{return_code} - output was \"#{stdout}\", error output was \"#{stderr}\"")
118
+ end
119
+ end
120
+ true
121
+ end
122
+ end
123
+
55
124
  def interpreter_path
56
125
  # Powershell.exe is always in "v1.0" folder (for backwards compatibility)
57
126
  # pwsh is the other interpreter and we will assume that it is on the path.
@@ -69,7 +138,7 @@ class Chef
69
138
  end
70
139
 
71
140
  def code
72
- code = wrapper_script
141
+ code = powershell_wrapper_script
73
142
  logger.trace("powershell_script provider called with script code:\n\n#{new_resource.code}\n")
74
143
  logger.trace("powershell_script provider will execute transformed code:\n\n#{code}\n")
75
144
  code
@@ -129,7 +198,11 @@ class Chef
129
198
  # last process run in the script if it is the last command
130
199
  # executed, otherwise 0 or 1 based on whether $? is set to true
131
200
  # (success, where we return 0) or false (where we return 1).
132
- def wrapper_script
201
+ #
202
+ # This is the regular powershell version of the above script - the difference
203
+ # is that regular powershell allows for hidden visibility variables, due to the
204
+ # very slightly different semantics.
205
+ def powershell_wrapper_script
133
206
  <<~EOH
134
207
  # Chef Client wrapper for powershell_script resources
135
208
 
@@ -148,12 +221,23 @@ class Chef
148
221
  # Catch any exceptions -- without this, exceptions will result
149
222
  # In a zero return code instead of the desired non-zero code
150
223
  # that indicates a failure
224
+
225
+ #{if new_resource.use_inline_powershell
226
+ # Inline powershell doesn't allow for private visibility variables,
227
+ # and uses return instead of exit
228
+ <<-EOI
229
+ trap [Exception] {write-error ($_.Exception.Message);return 1}
230
+ $interpolatedexitcode = $#{new_resource.convert_boolean_return}
231
+ EOI
232
+ else
233
+ <<-EOI
151
234
  trap [Exception] {write-error ($_.Exception.Message);exit 1}
152
235
 
153
236
  # Variable state that should not be accessible to the user code
154
237
  new-variable -name interpolatedexitcode -visibility private -value $#{new_resource.convert_boolean_return}
155
238
  new-variable -name chefscriptresult -visibility private
156
-
239
+ EOI
240
+ end}
157
241
  # Initialize a variable we use to capture $? inside a block
158
242
  $global:lastcmdlet = $null
159
243
 
@@ -198,7 +282,13 @@ class Chef
198
282
  # status of PowerShell.exe will be $exitstatus. If it was
199
283
  # launched with -Command, it will be 0 if $exitstatus was 0,
200
284
  # 1 (i.e. failed) otherwise.
201
- exit $exitstatus
285
+ #{if new_resource.use_inline_powershell
286
+ # Inline powershell needs return, not exit
287
+ "return $exitstatus\n"
288
+ else
289
+ "exit $exitstatus\n"
290
+ end
291
+ }
202
292
  EOH
203
293
  end
204
294
 
@@ -51,6 +51,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
51
51
  current_resource.running(false)
52
52
  current_resource.enabled(false)
53
53
  current_resource.masked(false)
54
+ current_resource.static(false)
54
55
  current_resource.indirect(false)
55
56
  end
56
57
  else
@@ -59,6 +60,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
59
60
 
60
61
  current_resource.enabled(is_enabled?)
61
62
  current_resource.masked(is_masked?)
63
+ current_resource.static(is_static?)
62
64
  current_resource.indirect(is_indirect?)
63
65
  current_resource
64
66
  end
@@ -168,20 +170,29 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
168
170
  end
169
171
  end
170
172
 
171
- def enable_service
172
- if current_resource.masked || current_resource.indirect
173
- logger.debug("#{new_resource} cannot be enabled: it is masked or indirect")
174
- return
173
+ def enableable?(action)
174
+ if current_resource.masked
175
+ logger.debug("#{new_resource} cannot be #{action}d: it is masked")
176
+ return false
177
+ end
178
+ if current_resource.static
179
+ logger.debug("#{new_resource} cannot be #{action}d: it is static")
180
+ return false
181
+ end
182
+ if current_resource.indirect
183
+ logger.debug("#{new_resource} cannot be #{action}d: it is indirect")
184
+ return false
175
185
  end
186
+ true
187
+ end
188
+
189
+ def enable_service
190
+ # This function can safely assume that enableable? is true
176
191
  options, args = get_systemctl_options_args
177
192
  shell_out!(systemctl_path, args, "enable", new_resource.service_name, **options)
178
193
  end
179
194
 
180
195
  def disable_service
181
- if current_resource.masked || current_resource.indirect
182
- logger.debug("#{new_resource} cannot be disabled: it is masked or indirect")
183
- return
184
- end
185
196
  options, args = get_systemctl_options_args
186
197
  shell_out!(systemctl_path, args, "disable", new_resource.service_name, **options)
187
198
  end
@@ -217,6 +228,10 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
217
228
  systemd_service_status["UnitFileState"] == "indirect"
218
229
  end
219
230
 
231
+ def is_static?
232
+ systemd_service_status["UnitFileState"] == "static"
233
+ end
234
+
220
235
  def is_masked?
221
236
  # Note: masked-runtime is excluded, because runtime is volatile, and
222
237
  # because masked-runtime is not masked.
@@ -74,6 +74,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
74
74
  current_resource.run_as_user(config_info.service_start_name) if config_info.service_start_name
75
75
  current_resource.display_name(config_info.display_name) if config_info.display_name
76
76
  current_resource.delayed_start(current_delayed_start) if current_delayed_start
77
+ current_resource.description(config_info.description) if new_resource.description
77
78
  end
78
79
 
79
80
  current_resource
@@ -80,7 +80,19 @@ class Chef
80
80
  end
81
81
  end
82
82
 
83
+ # This is a hook for subclasses to be able to tell the super class that a
84
+ # service is or is not enable-able. For example, on systemd, static and
85
+ # indirect units are not enable-able.
86
+ #
87
+ # In addition, this method offloads the messaging to the user. If the
88
+ # method returns `false`, it should say why `action` couldn't be taken
89
+ def enableable?(action)
90
+ true
91
+ end
92
+
83
93
  action :enable do
94
+ return unless enableable?(:enable)
95
+
84
96
  if current_resource.enabled
85
97
  logger.debug("#{new_resource} already enabled - nothing to do")
86
98
  else
@@ -94,6 +106,8 @@ class Chef
94
106
  end
95
107
 
96
108
  action :disable do
109
+ return unless enableable?(:disable)
110
+
97
111
  if current_resource.enabled
98
112
  converge_by("disable service #{new_resource}") do
99
113
  disable_service
@@ -153,7 +153,11 @@ class Chef
153
153
  new_val = new_resource.send(user_attrib)
154
154
  cur_val = current_resource.send(user_attrib)
155
155
  if !new_val.nil? && new_val.to_s != cur_val.to_s
156
- @change_desc << "change #{user_attrib} from #{cur_val} to #{new_val}"
156
+ if user_attrib.to_s == "password" && new_resource.sensitive
157
+ @change_desc << "change #{user_attrib} from ******** to ********"
158
+ else
159
+ @change_desc << "change #{user_attrib} from #{cur_val} to #{new_val}"
160
+ end
157
161
  end
158
162
  end
159
163
 
data/lib/chef/recipe.rb CHANGED
@@ -70,20 +70,12 @@ class Chef
70
70
 
71
71
  # This was moved to Chef::Node#tag, redirecting here for compatibility
72
72
  def tag(*tags)
73
- run_context.node.tag(*tags)
73
+ node.tag(*tags)
74
74
  end
75
75
 
76
- # Removes the list of tags from the node.
77
- #
78
- # === Parameters
79
- # tags<Array>:: A list of tags
80
- #
81
- # === Returns
82
- # tags<Array>:: The current list of run_context.node.tags
76
+ # This was moved to Chef::Node#untag, redirecting here for compatibility
83
77
  def untag(*tags)
84
- tags.each do |tag|
85
- run_context.node.tags.delete(tag)
86
- end
78
+ node.untag(*tags)
87
79
  end
88
80
 
89
81
  def from_yaml_file(filename)
@@ -351,7 +351,7 @@ action_class do
351
351
  Chef.run_context.transport.connection
352
352
  end
353
353
 
354
- # Remove all empty keys (recusively) from Hash.
354
+ # Remove all empty keys (recursively) from Hash.
355
355
  # @see https://stackoverflow.com/questions/56457020/#answer-56458673
356
356
  def deep_compact!(hsh)
357
357
  raise TypeError unless hsh.is_a? Hash