chef 18.4.12 → 18.6.2

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