chef 18.3.0 → 18.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|