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.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/chef.gemspec +3 -3
- data/lib/chef/chef_fs/file_pattern.rb +1 -1
- data/lib/chef/chef_fs/path_utils.rb +7 -9
- data/lib/chef/cookbook/synchronizer.rb +2 -3
- data/lib/chef/delayed_evaluator.rb +4 -0
- data/lib/chef/file_access_control/windows.rb +4 -1
- data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +2 -0
- data/lib/chef/mixin/homebrew_user.rb +6 -3
- data/lib/chef/monkey_patches/net-http.rb +5 -5
- data/lib/chef/node/attribute_collections.rb +2 -1
- data/lib/chef/node/immutable_collections.rb +2 -1
- data/lib/chef/node/mixin/state_tracking_array.rb +41 -0
- data/lib/chef/node.rb +22 -0
- data/lib/chef/provider/package/apt.rb +11 -2
- data/lib/chef/provider/package/chocolatey.rb +228 -24
- data/lib/chef/provider/package/zypper.rb +5 -0
- data/lib/chef/provider/powershell_script.rb +96 -6
- data/lib/chef/provider/service/systemd.rb +23 -8
- data/lib/chef/provider/service/windows.rb +1 -0
- data/lib/chef/provider/service.rb +14 -0
- data/lib/chef/provider/user.rb +5 -1
- data/lib/chef/recipe.rb +3 -11
- data/lib/chef/resource/_rest_resource.rb +1 -1
- data/lib/chef/resource/apt_package.rb +19 -0
- data/lib/chef/resource/apt_repository.rb +2 -1
- data/lib/chef/resource/chef_client_systemd_timer.rb +1 -1
- data/lib/chef/resource/chocolatey_installer.rb +207 -0
- data/lib/chef/resource/chocolatey_package.rb +8 -0
- data/lib/chef/resource/homebrew_package.rb +1 -1
- data/lib/chef/resource/locale.rb +5 -2
- data/lib/chef/resource/macos_pkg.rb +111 -0
- data/lib/chef/resource/powershell_script.rb +5 -1
- data/lib/chef/resource/service.rb +3 -0
- data/lib/chef/resource/sudo.rb +37 -2
- data/lib/chef/resource/support/ulimit.erb +40 -0
- data/lib/chef/resource/user_ulimit.rb +38 -0
- data/lib/chef/resources.rb +2 -0
- data/lib/chef/version.rb +1 -1
- data/spec/data/trusted_certs/example.crt +29 -20
- data/spec/data/trusted_certs/example_no_cn.crt +30 -34
- data/spec/functional/resource/chocolatey_package_spec.rb +2 -2
- data/spec/functional/resource/zypper_package_spec.rb +10 -0
- data/spec/unit/compliance/reporter/chef_server_automate_spec.rb +1 -1
- data/spec/unit/delayed_evaluator_spec.rb +35 -0
- data/spec/unit/node_spec.rb +6 -0
- data/spec/unit/provider/package/apt_spec.rb +18 -13
- data/spec/unit/provider/package/chocolatey_spec.rb +40 -12
- data/spec/unit/provider/powershell_script_spec.rb +100 -4
- data/spec/unit/provider/service/systemd_service_spec.rb +1 -0
- data/spec/unit/provider/user/linux_spec.rb +10 -0
- data/spec/unit/resource/chef_client_systemd_timer_spec.rb +1 -1
- data/spec/unit/resource/chocolatey_installer_spec.rb +151 -0
- data/spec/unit/resource/macos_pkg_spec.rb +38 -0
- data/spec/unit/resource/powershell_script_spec.rb +2 -2
- 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 '
|
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
|
134
|
-
@get_choco_version ||= powershell_exec!("
|
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
|
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
|
-
|
145
|
-
|
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
|
-
|
179
|
-
|
180
|
-
|
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
|
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
|
-
|
240
|
-
|
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
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
31
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
172
|
-
if current_resource.masked
|
173
|
-
logger.debug("#{new_resource} cannot be
|
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
|
data/lib/chef/provider/user.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
73
|
+
node.tag(*tags)
|
74
74
|
end
|
75
75
|
|
76
|
-
#
|
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
|
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 (
|
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
|