chef 18.3.0 → 18.4.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/chef.gemspec +2 -2
  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 +23 -1
  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 +22 -10
@@ -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