chef 18.4.12 → 18.6.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +9 -2
  3. data/chef-universal-mingw-ucrt.gemspec +1 -1
  4. data/chef.gemspec +8 -8
  5. data/lib/chef/client.rb +0 -15
  6. data/lib/chef/cookbook/chefignore.rb +4 -1
  7. data/lib/chef/cookbook/cookbook_version_loader.rb +1 -1
  8. data/lib/chef/cookbook/remote_file_vendor.rb +3 -2
  9. data/lib/chef/cookbook/synchronizer.rb +2 -1
  10. data/lib/chef/cookbook_manifest.rb +2 -2
  11. data/lib/chef/exceptions.rb +1 -1
  12. data/lib/chef/file_cache.rb +17 -2
  13. data/lib/chef/formatters/doc.rb +1 -1
  14. data/lib/chef/mixin/{homebrew_user.rb → homebrew.rb} +13 -16
  15. data/lib/chef/mixin/openssl_helper.rb +2 -13
  16. data/lib/chef/node/attribute.rb +3 -11
  17. data/lib/chef/node/immutable_collections.rb +15 -8
  18. data/lib/chef/node/mixin/state_tracking.rb +6 -3
  19. data/lib/chef/policy_builder/policyfile.rb +8 -0
  20. data/lib/chef/provider/package/chocolatey.rb +56 -26
  21. data/lib/chef/provider/package/homebrew.rb +6 -9
  22. data/lib/chef/provider/package/powershell.rb +1 -0
  23. data/lib/chef/provider/package/rubygems.rb +4 -0
  24. data/lib/chef/provider/package/snap.rb +1 -0
  25. data/lib/chef/provider/package/zypper.rb +0 -1
  26. data/lib/chef/provider/service/windows.rb +0 -1
  27. data/lib/chef/provider/user/windows.rb +5 -0
  28. data/lib/chef/resource/chef_client_config.rb +4 -2
  29. data/lib/chef/resource/chef_client_systemd_timer.rb +5 -0
  30. data/lib/chef/resource/chef_gem.rb +1 -1
  31. data/lib/chef/resource/execute.rb +8 -6
  32. data/lib/chef/resource/habitat_install.rb +2 -1
  33. data/lib/chef/resource/homebrew_cask.rb +19 -30
  34. data/lib/chef/resource/homebrew_tap.rb +32 -17
  35. data/lib/chef/resource/homebrew_update.rb +4 -4
  36. data/lib/chef/resource/powershell_package.rb +4 -0
  37. data/lib/chef/resource/snap_package.rb +23 -0
  38. data/lib/chef/resource/support/client.erb +4 -3
  39. data/lib/chef/resource/sysctl.rb +1 -0
  40. data/lib/chef/resource_inspector.rb +25 -7
  41. data/lib/chef/version.rb +1 -1
  42. data/lib/chef/win32/registry.rb +5 -0
  43. data/lib/chef/win32/security.rb +9 -0
  44. data/spec/functional/resource/cookbook_file_spec.rb +1 -1
  45. data/spec/functional/resource/remote_file_spec.rb +1 -1
  46. data/spec/integration/client/fips_spec.rb +11 -2
  47. data/spec/integration/client/open_ssl_spec.rb +20 -0
  48. data/spec/spec_helper.rb +4 -1
  49. data/spec/support/chef_helpers.rb +2 -2
  50. data/spec/support/platform_helpers.rb +28 -7
  51. data/spec/support/shared/functional/file_resource.rb +3 -3
  52. data/spec/unit/client_spec.rb +0 -16
  53. data/spec/unit/file_cache_spec.rb +64 -0
  54. data/spec/unit/mixin/homebrew_spec.rb +118 -0
  55. data/spec/unit/mixin/openssl_helper_spec.rb +6 -1
  56. data/spec/unit/provider/package/chocolatey_spec.rb +17 -12
  57. data/spec/unit/provider/package/homebrew_spec.rb +4 -1
  58. data/spec/unit/provider/package/windows_spec.rb +5 -5
  59. data/spec/unit/provider/package/zypper_spec.rb +0 -10
  60. data/spec/unit/provider/user/windows_spec.rb +1 -0
  61. data/spec/unit/resource_inspector_spec.rb +36 -0
  62. metadata +23 -10
  63. data/spec/unit/mixin/homebrew_user_spec.rb +0 -119
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d054a5d4b494b34b37e06e230641502c973712e05466d2112b3bbd69be500d26
4
- data.tar.gz: 8217503105a7dc435ad2928a1760ee6403102bdeba006d3d959f368bef3fa8bd
3
+ metadata.gz: 9758afdcd61880d10d2e6fcbd0097a3dc94ead3ce67004ba317ed1754cf529d2
4
+ data.tar.gz: 2ea4ff22776735a4ec71531e3c188769328e3dab07f1930ab4f59c6c2032a9b4
5
5
  SHA512:
6
- metadata.gz: fca7e74fe02c2070fd34f30a58c5206a56ed35fc11dc378a9d60cad12dd17cc5a3ef3f293f4ca4e218b54edc9b62ce186fb7328883786261995846a696e1e54f
7
- data.tar.gz: 59dceca64109ceb153dee38a72b1865f0a582c1b48f257d14462c6450ae97a3d52febb946d37b15afba21d5bb9132c32fc1df751a081b977a883add135d1629e
6
+ metadata.gz: 6f54220fa4591d43a562055250981453252a30db5bed94473ba0350b4d147ae0eb517da7ab371ef5963b8c7675b06206a5366585a86234ef0ede60a2d92ebf28
7
+ data.tar.gz: d096c1a73764313a572382cb45963efac685f4a33655225b447b8f58f9dbcfb4abf732da9c7bd2c855bf526a28d195a2a3af49e36029b9f7941bbe4b2ad6682b
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gem "chef", path: "."
4
4
 
5
- gem "ohai", git: "https://github.com/chef/ohai.git", branch: "main"
5
+ gem "ohai", git: "https://github.com/chef/ohai.git", branch: "18-stable"
6
6
 
7
7
  # Nwed to file a bug with rest-client. In the meantime, we can use this until they accept the update.
8
8
  gem "rest-client", git: "https://github.com/chef/rest-client", branch: "jfm/ucrt_update1"
@@ -11,6 +11,13 @@ gem "ffi", ">= 1.15.5"
11
11
  gem "chef-utils", path: File.expand_path("chef-utils", __dir__) if File.exist?(File.expand_path("chef-utils", __dir__))
12
12
  gem "chef-config", path: File.expand_path("chef-config", __dir__) if File.exist?(File.expand_path("chef-config", __dir__))
13
13
 
14
+ # required for FIPS or bundler will pick up default openssl
15
+ install_if -> { RUBY_PLATFORM !~ /darwin/ } do
16
+ gem "openssl", "= 3.2.0"
17
+ end
18
+
19
+ gem "rdoc", "~> 6.4.1" # 6.4.1.1 required for CVE-2024-27281, allow patch upgrades
20
+
14
21
  if File.exist?(File.expand_path("chef-bin", __dir__))
15
22
  # bundling in a git checkout
16
23
  gem "chef-bin", path: File.expand_path("chef-bin", __dir__)
@@ -49,7 +56,7 @@ group(:knife_windows_deps) do
49
56
  end
50
57
 
51
58
  group(:development, :test) do
52
- gem "rake"
59
+ gem "rake", ">= 12.3.3"
53
60
  gem "rspec"
54
61
  gem "webmock"
55
62
  gem "crack", "< 0.4.6" # due to https://github.com/jnunemaker/crack/pull/75
@@ -1,6 +1,6 @@
1
1
  gemspec = instance_eval(File.read(File.expand_path("chef.gemspec", __dir__)))
2
2
 
3
- gemspec.platform = Gem::Platform.new(%w{x64-mingw-ucrt})
3
+ gemspec.platform = Gem::Platform.new(%w{universal mingw-ucrt})
4
4
 
5
5
  gemspec.add_dependency "win32-api", "~> 1.10.0"
6
6
  gemspec.add_dependency "win32-event", "~> 0.6.1"
data/chef.gemspec CHANGED
@@ -2,12 +2,12 @@ $:.unshift(File.dirname(__FILE__) + "/lib")
2
2
  vs_path = File.expand_path("chef-utils/lib/chef-utils/version_string.rb", __dir__)
3
3
 
4
4
  if File.exist?(vs_path)
5
- # this is the moral equivalent of a require_relative since bundler makes require_relative here fail hard
6
- eval(IO.read(vs_path))
7
- else
8
- # if the path doesn't exist then we're just in the wild gem and not in the git repo
9
- require "chef-utils/version_string"
5
+ # include chef-utils/lib in the path if we're inside of chef vs. chef-utils gem
6
+ # but add it to the end of the search path
7
+ $: << (File.dirname(__FILE__) + "/chef-utils/lib")
10
8
  end
9
+ # if the path doesn't exist then we're just in the wild gem and not in the git repo
10
+ require "chef-utils/version_string"
11
11
  require "chef/version"
12
12
 
13
13
  Gem::Specification.new do |s|
@@ -30,7 +30,7 @@ Gem::Specification.new do |s|
30
30
 
31
31
  s.add_dependency "chef-config", "= #{Chef::VERSION}"
32
32
  s.add_dependency "chef-utils", "= #{Chef::VERSION}"
33
- s.add_dependency "train-core", "~> 3.10" # 3.2.28 fixes sudo prompts. See https://github.com/chef/chef/pull/9635
33
+ s.add_dependency "train-core", "~> 3.10", "<= 3.12.7"
34
34
  s.add_dependency "train-winrm", ">= 0.2.5"
35
35
  s.add_dependency "train-rest", ">= 0.4.1" # target mode with rest APIs
36
36
 
@@ -43,7 +43,7 @@ Gem::Specification.new do |s|
43
43
  s.add_dependency "ohai", "~> 18.0"
44
44
  s.add_dependency "inspec-core", ">= 5", "< 6"
45
45
 
46
- s.add_dependency "ffi", ">= 1.15.5"
46
+ s.add_dependency "ffi", ">= 1.15.5", "<= 1.16.3"
47
47
  s.add_dependency "ffi-yajl", "~> 2.2"
48
48
  s.add_dependency "net-sftp", ">= 2.1.2", "< 5.0" # remote_file resource
49
49
  s.add_dependency "net-ftp" # remote_file resource
@@ -65,7 +65,7 @@ Gem::Specification.new do |s|
65
65
 
66
66
  s.add_dependency "aws-sdk-s3", "~> 1.91" # s3 recipe-url support
67
67
  s.add_dependency "aws-sdk-secretsmanager", "~> 1.46"
68
- s.add_dependency "vault", "~> 0.16" # hashi vault official client gem
68
+ s.add_dependency "vault", "~> 0.18.2" # hashi vault official client gem
69
69
  s.bindir = "bin"
70
70
  s.executables = %w{ }
71
71
 
data/lib/chef/client.rb CHANGED
@@ -305,8 +305,6 @@ class Chef
305
305
  # keep this inside the main loop to get exception backtraces
306
306
  end_profiling
307
307
 
308
- warn_if_eol
309
-
310
308
  # rebooting has to be the last thing we do, no exceptions.
311
309
  Chef::Platform::Rebooter.reboot_if_needed!(node)
312
310
  rescue Exception => run_error
@@ -335,19 +333,6 @@ class Chef
335
333
  # @todo make this stuff protected or private
336
334
  #
337
335
 
338
- # @api private
339
- def warn_if_eol
340
- require_relative "version"
341
-
342
- # We make a release every year so take the version you're on + 2006 and you get
343
- # the year it goes EOL
344
- eol_year = 2006 + Gem::Version.new(Chef::VERSION).segments.first
345
-
346
- if Time.now > Time.new(eol_year, 5, 01)
347
- logger.warn("This release of #{ChefUtils::Dist::Infra::PRODUCT} became end of life (EOL) on May 1st #{eol_year}. Please update to a supported release to receive new features, bug fixes, and security updates.")
348
- end
349
- end
350
-
351
336
  # @api private
352
337
  def configure_formatters
353
338
  formatters_for_run.map do |formatter_name, output_path|
@@ -50,7 +50,10 @@ class Chef
50
50
  ignore_globs = []
51
51
  if @ignore_file && readable_file_or_symlink?(@ignore_file)
52
52
  File.foreach(@ignore_file) do |line|
53
- ignore_globs << line.strip unless COMMENTS_AND_WHITESPACE.match?(line)
53
+ unless COMMENTS_AND_WHITESPACE.match?(line)
54
+ line.strip!
55
+ ignore_globs << line
56
+ end
54
57
  end
55
58
  else
56
59
  Chef::Log.debug("No chefignore file found. No files will be ignored!")
@@ -215,7 +215,7 @@ class Chef
215
215
  Dir.entries(cookbook_path).each do |top_filename|
216
216
  # Skip top-level directories starting with "."
217
217
  top_path = File.join(cookbook_path, top_filename)
218
- next if File.directory?(top_path) && top_filename.start_with?(".")
218
+ next if top_filename.start_with?(".") && File.directory?(top_path)
219
219
 
220
220
  # Use Find.find because it:
221
221
  # (a) returns any children, recursively
@@ -43,9 +43,10 @@ class Chef
43
43
  raise "get_filename: Cannot determine segment/filename for incoming filename #{filename}"
44
44
  end
45
45
 
46
- raise "No such segment #{segment} in cookbook #{@cookbook_name}" unless @manifest.files_for(segment)
46
+ files_for_segment = @manifest.files_for(segment)
47
+ raise "No such segment #{segment} in cookbook #{@cookbook_name}" unless files_for_segment
47
48
 
48
- found_manifest_record = @manifest.files_for(segment).find { |manifest_record| manifest_record[:path] == filename }
49
+ found_manifest_record = files_for_segment.find { |manifest_record| manifest_record[:path] == filename }
49
50
  raise "No such file #{filename} in #{@cookbook_name}" unless found_manifest_record
50
51
 
51
52
  cache_filename = File.join("cookbooks", @cookbook_name, found_manifest_record["path"])
@@ -280,8 +280,9 @@ class Chef
280
280
  end
281
281
 
282
282
  def ensure_cookbook_paths
283
+ cookbook_path = File.join(Chef::Config[:file_cache_path], "cookbooks")
283
284
  cookbooks.each do |cookbook|
284
- cb_dir = File.join(Chef::Config[:file_cache_path], "cookbooks", cookbook.name)
285
+ cb_dir = File.join(cookbook_path, cookbook.name)
285
286
  cookbook.root_paths = Array(cb_dir)
286
287
  end
287
288
  end
@@ -173,9 +173,9 @@ class Chef
173
173
  def files_for(part)
174
174
  return root_files if part.to_s == "root_files"
175
175
 
176
+ part_match = "#{part}/"
176
177
  manifest[:all_files].select do |file|
177
- seg = file[:name].split("/")[0]
178
- part.to_s == seg
178
+ file[:name].start_with?(part_match)
179
179
  end
180
180
  end
181
181
 
@@ -170,7 +170,7 @@ class Chef
170
170
  class PowershellCmdletException < RuntimeError; end
171
171
  class LCMParser < RuntimeError; end
172
172
 
173
- class CannotDetermineHomebrewOwner < Package; end
173
+ class CannotDetermineHomebrewPath < Package; end
174
174
  class CannotDetermineWindowsInstallerType < Package; end
175
175
  class NoWindowsPackageSource < Package; end
176
176
 
@@ -159,9 +159,24 @@ class Chef
159
159
  # [String] - An array of file cache keys matching the glob
160
160
  def find(glob_pattern)
161
161
  keys = []
162
- Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(file_cache_path), glob_pattern)].each do |f|
162
+ file_cache_dir = Chef::Util::PathHelper.escape_glob_dir(file_cache_path)
163
+ first_filename = Dir[file_cache_dir].first # directory of the cache
164
+ return keys unless first_filename
165
+
166
+ # TODO: The usage of Regexp.escape and the match here is likely
167
+ # vestigial, but since it's only getting called once per method, the
168
+ # effort needed to confirm that its removal won't break something else
169
+ # isn't worth it. A task for a brave soul ;-)
170
+ regexp_pattern = /^(#{Regexp.escape(first_filename) + File::Separator}).+/
171
+
172
+ files = Dir[File.join(file_cache_dir, glob_pattern)]
173
+ until files.empty?
174
+ f = files.shift
163
175
  if File.file?(f)
164
- keys << f[/^#{Regexp.escape(Dir[Chef::Util::PathHelper.escape_glob_dir(file_cache_path)].first) + File::Separator}(.+)/, 1]
176
+ # We remove the cache directory from the string of each entry
177
+ path_to_remove ||= f[regexp_pattern, 1]
178
+ f.delete_prefix!(path_to_remove)
179
+ keys << f
165
180
  end
166
181
  end
167
182
  keys
@@ -57,7 +57,7 @@ class Chef
57
57
  # Print out deprecations.
58
58
  unless deprecations.empty?
59
59
  puts_line ""
60
- puts_line "Deprecation warnings that must be addressed before upgrading to Chef Infra #{Chef::VERSION.to_i + 1}:"
60
+ puts_line "Deprecation warnings that must be addressed before upgrading to #{ChefUtils::Dist::Infra::PRODUCT} #{Chef::VERSION.to_i + 1}:"
61
61
  puts_line ""
62
62
  deprecations.each do |message, details|
63
63
  locations = details[:locations]
@@ -27,7 +27,7 @@ require "etc" unless defined?(Etc)
27
27
 
28
28
  class Chef
29
29
  module Mixin
30
- module HomebrewUser
30
+ module Homebrew
31
31
  include Chef::Mixin::ShellOut
32
32
 
33
33
  ##
@@ -57,15 +57,19 @@ class Chef
57
57
  @homebrew_owner_username
58
58
  end
59
59
 
60
+ # Use homebrew_bin_path to return the path to the brew binary
61
+ # @param [String, Array(String)] brew_bin_path
62
+ # @return [String] path to the brew binary
60
63
  def homebrew_bin_path(brew_bin_path = nil)
61
64
  if brew_bin_path && ::File.exist?(brew_bin_path)
62
65
  brew_bin_path
63
66
  else
64
- [which("brew"), "/opt/homebrew/bin/brew", "/usr/local/bin/brew", "/home/linuxbrew/.linuxbrew/bin/brew"].uniq.select do |x|
65
- next if x == false
67
+ brew_path = which("brew", prepend_path: %w{/opt/homebrew/bin /usr/local/bin /home/linuxbrew/.linuxbrew/bin})
68
+ unless brew_path
69
+ raise Chef::Exceptions::CannotDetermineHomebrewPath, 'Couldn\'t find the "brew" executable anywhere on the path.'
70
+ end
66
71
 
67
- ::File.exist?(x) && ::File.executable?(x)
68
- end.first || nil
72
+ brew_path
69
73
  end
70
74
  end
71
75
 
@@ -73,18 +77,11 @@ class Chef
73
77
 
74
78
  def calculate_owner
75
79
  brew_path = homebrew_bin_path
76
- if brew_path
77
- # By default, this follows symlinks which is what we want
78
- owner = ::File.stat(brew_path).uid
79
- else
80
- raise Chef::Exceptions::CannotDetermineHomebrewOwner,
81
- 'Couldn\'t find the "brew" executable anywhere on the path.'
82
- end
83
-
84
- Chef::Log.debug "Found Homebrew owner #{Etc.getpwuid(owner).name}; executing `brew` commands as them"
85
- owner
80
+ # By default, this follows symlinks which is what we want
81
+ owner_uid = ::File.stat(brew_path).uid
82
+ Chef::Log.debug "Found Homebrew owner #{Etc.getpwuid(owner_uid).name}; executing `brew` commands as them"
83
+ owner_uid
86
84
  end
87
-
88
85
  end
89
86
  end
90
87
  end
@@ -157,7 +157,7 @@ class Chef
157
157
  raise TypeError, "curve must be a string" unless curve.is_a?(String)
158
158
  raise ArgumentError, "Specified curve is not available on this system" unless %w{prime256v1 secp384r1 secp521r1}.include?(curve)
159
159
 
160
- ::OpenSSL::PKey::EC.new(curve).generate_key
160
+ ::OpenSSL::PKey::EC.generate(curve)
161
161
  end
162
162
 
163
163
  # generate pem format of the public key given a private key
@@ -170,18 +170,7 @@ class Chef
170
170
  key_content = ::File.exist?(priv_key) ? File.read(priv_key) : priv_key
171
171
  key = ::OpenSSL::PKey::EC.new key_content, priv_key_password
172
172
 
173
- # Get curve type (prime256v1...)
174
- group = ::OpenSSL::PKey::EC::Group.new(key.group.curve_name)
175
- # Get Generator point & public point (priv * generator)
176
- generator = group.generator
177
- pub_point = generator.mul(key.private_key)
178
- key.public_key = pub_point
179
-
180
- # Public Key in pem
181
- public_key = ::OpenSSL::PKey::EC.new
182
- public_key.group = group
183
- public_key.public_key = pub_point
184
- public_key.to_pem
173
+ key.public_to_pem
185
174
  end
186
175
 
187
176
  # generate a pem file given a cipher, key, an optional key_password
@@ -570,7 +570,7 @@ class Chef
570
570
  ]
571
571
 
572
572
  ret = components.inject(NIL) do |merged, component|
573
- hash_only_merge!(merged, component)
573
+ component == NIL ? merged : hash_only_merge!(merged, component)
574
574
  end
575
575
  ret == NIL ? nil : ret
576
576
  end
@@ -584,7 +584,7 @@ class Chef
584
584
  def merge_defaults(path)
585
585
  DEFAULT_COMPONENTS.inject(NIL) do |merged, component_ivar|
586
586
  component_value = apply_path(instance_variable_get(component_ivar), path)
587
- deep_merge!(merged, component_value)
587
+ component_value == NIL ? merged : deep_merge!(merged, component_value)
588
588
  end
589
589
  end
590
590
 
@@ -597,7 +597,7 @@ class Chef
597
597
  def merge_overrides(path)
598
598
  OVERRIDE_COMPONENTS.inject(NIL) do |merged, component_ivar|
599
599
  component_value = apply_path(instance_variable_get(component_ivar), path)
600
- deep_merge!(merged, component_value)
600
+ component_value == NIL ? merged : deep_merge!(merged, component_value)
601
601
  end
602
602
  end
603
603
 
@@ -628,10 +628,6 @@ class Chef
628
628
  elsif merge_onto.is_a?(Array) && merge_with.is_a?(Array)
629
629
  merge_onto |= merge_with
630
630
 
631
- # If merge_with is NIL, don't replace merge_onto
632
- elsif merge_with == NIL
633
- merge_onto
634
-
635
631
  # In all other cases, replace merge_onto with merge_with
636
632
  else
637
633
  if merge_with.is_a?(Hash)
@@ -661,10 +657,6 @@ class Chef
661
657
  end
662
658
  merge_onto
663
659
 
664
- # If merge_with is NIL, don't replace merge_onto
665
- elsif merge_with == NIL
666
- merge_onto
667
-
668
660
  # In all other cases, replace merge_onto with merge_with
669
661
  else
670
662
  if merge_with.is_a?(Hash)
@@ -33,18 +33,25 @@ class Chef
33
33
  end
34
34
 
35
35
  def convert_value(value)
36
- # The order in this case statement is *important*.
37
- # ImmutableMash and ImmutableArray should be tested first,
38
- # as this saves unnecessary creation of intermediate objects
39
36
  case value
40
- when ImmutableMash, ImmutableArray
41
- value
42
37
  when Hash
43
- ImmutableMash.new(value, __root__, __node__, __precedence__)
38
+ if ImmutableMash === value
39
+ # Save an object creation
40
+ value
41
+ else
42
+ ImmutableMash.new(value, __root__, __node__, __precedence__)
43
+ end
44
44
  when Array
45
- ImmutableArray.new(value, __root__, __node__, __precedence__)
45
+ if ImmutableArray === value
46
+ # Save an object creation
47
+ value
48
+ else
49
+ ImmutableArray.new(value, __root__, __node__, __precedence__)
50
+ end
46
51
  else
47
- safe_dup(value).freeze
52
+ # We return any already frozen strings, since that's common over the course of a run.
53
+ # Check `frozen?` first since that's faster than a Class comparison
54
+ value.frozen? && String === value ? value : safe_dup(value).freeze
48
55
  end
49
56
  end
50
57
 
@@ -37,7 +37,8 @@ class Chef
37
37
  def [](*args)
38
38
  ret = super
39
39
  key = args.first
40
- next_path = [ __path__, convert_key(key) ].flatten.compact
40
+ next_path = [ __path__, convert_key(key) ].flatten
41
+ next_path.compact!
41
42
  copy_state_to(ret, next_path)
42
43
  end
43
44
 
@@ -45,7 +46,8 @@ class Chef
45
46
  ret = super
46
47
  key = args.first
47
48
  value = args.last
48
- next_path = [ __path__, convert_key(key) ].flatten.compact
49
+ next_path = [ __path__, convert_key(key) ].flatten
50
+ next_path.compact!
49
51
  send_attribute_changed_event(next_path, value)
50
52
  copy_state_to(ret, next_path)
51
53
  end
@@ -77,7 +79,8 @@ class Chef
77
79
  end
78
80
 
79
81
  def send_reset_cache(path = nil, key = nil)
80
- next_path = [ path, key ].flatten.compact
82
+ next_path = [ path, key ].flatten
83
+ next_path.compact!
81
84
  __root__.reset_cache(next_path.first) if !__root__.nil? && __root__.respond_to?(:reset_cache)
82
85
  end
83
86
 
@@ -132,6 +132,9 @@ class Chef
132
132
 
133
133
  node.consume_external_attrs(ohai_data, json_attribs)
134
134
 
135
+ # Preserve the fall back to loading an unencrypted data bag item if the item we're trying to load isn't actually a vault item.
136
+ set_databag_fallback
137
+
135
138
  setup_run_list_override
136
139
 
137
140
  expand_run_list
@@ -191,6 +194,11 @@ class Chef
191
194
  run_context
192
195
  end
193
196
 
197
+ # Preserve the fall back to loading an unencrypted data bag item if the item we're trying to load isn't actually a vault item.
198
+ def set_databag_fallback
199
+ node.default["chef-vault"]["databag_fallback"] = ChefUtils.kitchen?(node)
200
+ end
201
+
194
202
  # Sets `run_list` on the node from the policy, sets `roles` and `recipes`
195
203
  # attributes on the node accordingly.
196
204
  #
@@ -144,12 +144,16 @@ class Chef
144
144
  def check_resource_semantics!; end
145
145
 
146
146
  def get_choco_version
147
- @get_choco_version ||= powershell_exec!("#{choco_exe} --version").result
147
+ # We need a different way to get the version than by simply calling "choco --version".
148
+ # If the license file is installed (for business customers) but not the Chocolatey.Extension (because you're using the choco resource to install it)
149
+ # then you get a license error. This method bypasses that by getting the version from the exe directly instead of invoking it.
150
+ # deprecated: @get_choco_version ||= powershell_exec!("#{choco_exe} --version").result
151
+ @get_choco_version ||= powershell_exec!("Get-ItemProperty #{choco_exe} | select-object -expandproperty versioninfo | select-object -expandproperty productversion").result
148
152
  end
149
153
 
150
154
  # Choco V2 uses 'Search' for remote repositories and 'List' for local packages
151
155
  def query_command
152
- return "list" if get_choco_version.match?(/^1/)
156
+ return "list" if Gem::Dependency.new("", "< 1.4.0").match?("", get_choco_version)
153
157
 
154
158
  "search"
155
159
  end
@@ -167,22 +171,34 @@ class Chef
167
171
  true
168
172
  end
169
173
 
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...
174
+ # Find the set of packages to ask the chocolatey server about
175
+ #
176
+ # if walk_resource_tree is true, this finds _all_ of the packages that
177
+ # we have referenced anywhere in our recipes - this is so we can
178
+ # attempt to query them all in a single transaction. However,
179
+ # currently we don't do that - see the comment on available_packages
180
+ # for details of the why, but the TL;DR is that the public chocolatey
181
+ # servers do not support `or` type queries properly.
182
+ #
183
+ # If walk_resource_tree is false, we don't do any of that - we just filter
184
+ # the package list based on cache data. This is the default due to reasons
185
+ # explained in the comment on available_packages - the goal is to eventually
186
+ # turn this back on, hence the default false parameter here.
175
187
  #
176
188
  # @return [Array] List of chocolatey packages referenced in the run list
177
- def collect_package_requests(ignore_list: [])
189
+ def collect_package_requests(ignore_list: [], walk_resource_tree: false)
178
190
  return ["*"] if new_resource.bulk_query || Chef::Config[:always_use_bulk_chocolatey_package_list]
179
191
 
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
192
+ if walk_resource_tree
193
+ # Get to the root of the resource collection
194
+ rc = run_context.parent_run_context || run_context
195
+ rc = rc.parent_run_context while rc.parent_run_context
183
196
 
184
- package_collection = package_name_array
185
- package_collection += nested_package_resources(rc.resource_collection)
197
+ package_collection = package_name_array
198
+ package_collection += nested_package_resources(rc.resource_collection)
199
+ else
200
+ package_collection = package_name_array
201
+ end
186
202
  # downcase the array and uniq. sorted for easier testing...
187
203
  package_collection.uniq.sort.filter { |pkg| !ignore_list.include?(pkg) }
188
204
  end
@@ -224,7 +240,7 @@ class Chef
224
240
  @choco_exe ||= begin
225
241
  # if this check is in #define_resource_requirements, it won't get
226
242
  # run before choco.exe gets called from #load_current_resource.
227
- exe_path = ::File.join(choco_install_path, "bin", "choco.exe")
243
+ exe_path = ::File.join(choco_install_path, "choco.exe")
228
244
  raise Chef::Exceptions::MissingLibrary, CHOCO_MISSING_MSG unless ::File.exist?(exe_path)
229
245
 
230
246
  exe_path
@@ -339,22 +355,33 @@ class Chef
339
355
  @@choco_available_packages[new_resource.list_options] = {}
340
356
  end
341
357
 
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
358
+ # This would previously grab 25 packages at a time, which previously worked - however,
359
+ # upstream changed and it turns out this was only working by accident - see
360
+ # https://github.com/chocolatey/choco/issues/2116 for this. So the TL;DR ends up
361
+ # being that this can be re-enabled when the chocolatey server actually supports an
362
+ # or operator. So it makes sense to leave the logic here for this split, while we
363
+ # work with upstream to get this to be a working feature there
364
+ #
365
+ # Foot guns: there is a --id-starts-with for chocolatey, which you'd think would work,
366
+ # but that actually fails on public chocolatey as well, because it seems to do the filtering
367
+ # locally. Which means it too will omit a lot of results (this is also corroborated by
368
+ # the 2116 issue above).
344
369
  #
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
370
+ # collect_package_requests, however, continues to be useful here because it filters
371
+ # the already cached things from the list. However, for now it will no longer walk the
372
+ # resource tree until 2116 can be sorted out. When we regain that ability, we should
373
+ # re-evaluate this, since it does save a LOT of API requests!
347
374
  collect_package_requests(
348
375
  ignore_list: @@choco_available_packages[new_resource.list_options].keys
349
- ).each_slice(25) do |pkg_set|
376
+ ).each do |pkg_set|
350
377
  available_versions =
351
378
  begin
352
379
  cmd = [ query_command, "-r" ]
353
380
 
354
381
  # Chocolatey doesn't actually take a wildcard for this query, however
355
382
  # it will return all packages when using '*' as a query
356
- unless pkg_set == ["*"]
357
- cmd += pkg_set
383
+ unless pkg_set == "*"
384
+ cmd << pkg_set
358
385
  end
359
386
  cmd += common_options
360
387
  cmd.push( new_resource.list_options ) if new_resource.list_options
@@ -383,10 +410,12 @@ class Chef
383
410
  #
384
411
  # @return [Hash] name-to-version mapping of installed packages
385
412
  def installed_packages
386
- if new_resource.use_choco_list == false || !Chef::Config[:always_use_choco_list]
387
- installed_packages_via_choco
388
- else
413
+ # Logic here must be either use_choco_list is false _and_ always_use_choco_list is
414
+ # falsy, since the global overrides the local
415
+ if new_resource.use_choco_list == false && !Chef::Config[:always_use_choco_list]
389
416
  installed_packages_via_disk
417
+ else
418
+ installed_packages_via_choco
390
419
  end
391
420
  end
392
421
 
@@ -413,8 +442,8 @@ class Chef
413
442
  # that contains all possible package folders, and so we push our
414
443
  # guess to the front as an optimization.
415
444
  target_dirs << targets.first.downcase if targets.length == 1
416
- if targets.downcase is_a?(String)
417
- target_dirs << targets
445
+ if targets.is_a?(String)
446
+ target_dirs << targets.downcase
418
447
  end
419
448
  target_dirs += get_local_pkg_dirs(choco_lib_path)
420
449
  fetch_package_versions(choco_lib_path, target_dirs, targets)
@@ -465,6 +494,7 @@ class Chef
465
494
  # Fetch the local package versions from chocolatey
466
495
  def fetch_package_versions(base_dir, target_dirs, targets)
467
496
  pkg_versions = {}
497
+ targets = [targets] if targets.is_a?(String)
468
498
  target_dirs.each do |dir|
469
499
  pkg_versions.merge!(get_pkg_data(::File.join(base_dir, dir)))
470
500
  # return early if we found the single package version we were looking for