summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2015-02-04 11:41:05 -0800
committerLamont Granquist <lamont@scriptkiddie.org>2015-02-04 11:41:05 -0800
commit6eae70225008c8b06601a8343a3f57cb10f3c065 (patch)
tree8580a82903860094def6c54c782022352739385f
parent0c05b112458ad62005e871dba22a7e44a845bf85 (diff)
parent4805025146896c537f3110aeceb1fba852897f8c (diff)
downloadchef-6eae70225008c8b06601a8343a3f57cb10f3c065.tar.gz
Merge pull request #2692 from jaymzh/multipackage
Multipackge support
-rw-r--r--CHANGELOG.md2
-rw-r--r--DOC_CHANGES.md5
-rw-r--r--RELEASE_NOTES.md5
-rw-r--r--lib/chef/mixin/get_source_from_package.rb1
-rw-r--r--lib/chef/provider/package.rb333
-rw-r--r--lib/chef/provider/package/apt.rb133
-rw-r--r--lib/chef/provider/package/rubygems.rb11
-rw-r--r--lib/chef/provider/package/yum.rb131
-rw-r--r--lib/chef/resource/package.rb4
-rw-r--r--spec/unit/provider/package/aix_spec.rb2
-rw-r--r--spec/unit/provider/package/apt_spec.rb8
-rw-r--r--spec/unit/provider/package/dpkg_spec.rb2
-rw-r--r--spec/unit/provider/package/ips_spec.rb3
-rw-r--r--spec/unit/provider/package/rubygems_spec.rb70
-rw-r--r--spec/unit/provider/package/solaris_spec.rb2
-rw-r--r--spec/unit/provider/package/yum_spec.rb139
-rw-r--r--spec/unit/provider/package_spec.rb276
-rw-r--r--spec/unit/provider/package_spec.rbe0
18 files changed, 938 insertions, 189 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c434650fb3..10829a08d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -50,6 +50,8 @@
Fix knife cookbook upload messages
* [**David Crowder**] (https://github.com/david-crowder)
refactor to use shell_out in rpm provider
+* [**Phil Dibowitz**](https://github.com/jaymzh):
+ Multi-package support
### Chef Contributions
* ruby 1.9.3 support is dropped
diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md
index dbe79478f5..4e11a6f957 100644
--- a/DOC_CHANGES.md
+++ b/DOC_CHANGES.md
@@ -39,3 +39,8 @@ This probably only needs to be a bullet point added to http://docs.getchef.com/n
## Drop SSL Warnings
Now that the default for SSL checking is on, no more warning is emitted when SSL
checking is off.
+
+## Multi-package Support
+The `package` provider has been extended to support multiple packages. This
+support is new and and not all subproviders yet support it. Full support for
+`apt` and `yum` has been implemented.
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 329f55555b..3c94bf21a9 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -68,6 +68,11 @@ Previously, when a URI scheme contained all uppercase letters, Chef would reject
Now that the default for SSL checking is on, no more warning is emitted when SSL
checking is off.
+## Multi-package Support
+The `package` provider has been extended to support multiple packages. This
+support is new and and not all subproviders yet support it. Full support for
+`apt` and `yum` has been implemented.
+
# Chef Client Release Notes 12.0.0:
# Internal API Changes in this Release
diff --git a/lib/chef/mixin/get_source_from_package.rb b/lib/chef/mixin/get_source_from_package.rb
index 6d5cb56a27..2ed251854a 100644
--- a/lib/chef/mixin/get_source_from_package.rb
+++ b/lib/chef/mixin/get_source_from_package.rb
@@ -29,6 +29,7 @@ class Chef
module GetSourceFromPackage
def initialize(new_resource, run_context)
super
+ return if new_resource.package_name.is_a?(Array)
# if we're passed something that looks like a filesystem path, with no source, use it
# - require at least one '/' in the path to avoid gem_package "foo" breaking if a file named 'foo' exists in the cwd
if new_resource.source.nil? && new_resource.package_name.match(/#{::File::SEPARATOR}/) && ::File.exists?(new_resource.package_name)
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index a4a056dfec..9edf8d5f52 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -25,9 +25,15 @@ class Chef
class Provider
class Package < Chef::Provider
+ # @todo: validate no subclasses need this and nuke it
include Chef::Mixin::Command
+ #
+ # Hook that subclasses use to populate the candidate_version(s)
+ #
+ # @return [Array, String] candidate_version(s) may be a string or array
attr_accessor :candidate_version
+
def initialize(new_resource, run_context)
super
@candidate_version = nil
@@ -41,63 +47,87 @@ class Chef
end
def define_resource_requirements
+ # XXX: upgrade with a specific version doesn't make a whole lot of sense, but why don't we throw this anyway if it happens?
+ # if not, shouldn't we raise to tell the user to use install instead of upgrade if they want to pin a version?
requirements.assert(:install) do |a|
- a.assertion { ((@new_resource.version != nil) && !(target_version_already_installed?)) \
- || !(@current_resource.version.nil? && candidate_version.nil?) }
- a.failure_message(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{@new_resource.package_name}")
- a.whyrun("Assuming a repository that offers #{@new_resource.package_name} would have been configured")
+ a.assertion { candidates_exist_for_all_forced_changes? }
+ a.failure_message(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{forced_packages_missing_candidates.join(", ")}")
+ a.whyrun("Assuming a repository that offers #{forced_packages_missing_candidates.join(", ")} would have been configured")
end
- requirements.assert(:upgrade) do |a|
- # Can't upgrade what we don't have
- a.assertion { !(@current_resource.version.nil? && candidate_version.nil?) }
- a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{@new_resource.package_name}")
- a.whyrun("Assuming a repository that offers #{@new_resource.package_name} would have been configured")
+ requirements.assert(:upgrade, :install) do |a|
+ a.assertion { candidates_exist_for_all_uninstalled? }
+ a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(", ")}")
+ a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(", ")} would have been configured")
end
end
def action_install
- # If we specified a version, and it's not the current version, move to the specified version
- if !@new_resource.version.nil? && !(target_version_already_installed?)
- install_version = @new_resource.version
- # If it's not installed at all, install it
- elsif @current_resource.version.nil?
- install_version = candidate_version
- else
+ unless target_version_array.any?
Chef::Log.debug("#{@new_resource} is already installed - nothing to do")
return
end
- # We need to make sure we handle the preseed file
+ # @todo: move the preseed code out of the base class (and complete the fix for Array of preseeds? ugh...)
if @new_resource.response_file
- if preseed_file = get_preseed_file(@new_resource.package_name, install_version)
- converge_by("preseed package #{@new_resource.package_name}") do
+ if preseed_file = get_preseed_file(package_names_for_targets, versions_for_targets)
+ converge_by("preseed package #{package_names_for_targets}") do
preseed_package(preseed_file)
end
end
end
- description = install_version ? "version #{install_version} of" : ""
- converge_by("install #{description} package #{@new_resource.package_name}") do
- @new_resource.version(install_version)
- install_package(@new_resource.package_name, install_version)
+
+ # XXX: mutating the new resource is generally bad
+ @new_resource.version(versions_for_new_resource)
+
+ converge_by(install_description) do
+ install_package(package_names_for_targets, versions_for_targets)
+ Chef::Log.info("#{@new_resource} installed #{package_names_for_targets} at #{versions_for_targets}")
end
end
+ def install_description
+ description = []
+ target_version_array.each_with_index do |target_version, i|
+ next if target_version.nil?
+ package_name = package_name_array[i]
+ description << "install version #{target_version} of package #{package_name}"
+ end
+ description
+ end
+
+ private :install_description
+
def action_upgrade
- if candidate_version.nil?
- Chef::Log.debug("#{@new_resource} no candidate version - nothing to do")
- elsif @current_resource.version == candidate_version
- Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
- else
- @new_resource.version(candidate_version)
- orig_version = @current_resource.version || "uninstalled"
- converge_by("upgrade package #{@new_resource.package_name} from #{orig_version} to #{candidate_version}") do
- upgrade_package(@new_resource.package_name, candidate_version)
- Chef::Log.info("#{@new_resource} upgraded from #{orig_version} to #{candidate_version}")
- end
+ if !target_version_array.any?
+ Chef::Log.debug("#{@new_resource} no versions to upgrade - nothing to do")
+ return
+ end
+
+ # XXX: mutating the new resource is generally bad
+ @new_resource.version(versions_for_new_resource)
+
+ converge_by(upgrade_description) do
+ upgrade_package(package_names_for_targets, versions_for_targets)
+ Chef::Log.info("#{@new_resource} upgraded #{package_names_for_targets} to #{versions_for_targets}")
end
end
+ def upgrade_description
+ description = []
+ target_version_array.each_with_index do |target_version, i|
+ next if target_version.nil?
+ package_name = package_name_array[i]
+ candidate_version = candidate_version_array[i]
+ current_version = current_version_array[i] || "uninstalled"
+ description << "upgrade package #{package_name} from #{current_version} to #{candidate_version}"
+ end
+ description
+ end
+
+ private :upgrade_description
+
+ # @todo: ability to remove an array of packages
def action_remove
if removing_package?
description = @new_resource.version ? "version #{@new_resource.version} of " : ""
@@ -110,18 +140,28 @@ class Chef
end
end
+ def have_any_matching_version?
+ f = []
+ new_version_array.each_with_index do |item, index|
+ f << (item == current_version_array[index])
+ end
+ f.any?
+ end
+
def removing_package?
- if @current_resource.version.nil?
- false # nothing to remove
- elsif @new_resource.version.nil?
- true # remove any version of a package
- elsif @new_resource.version == @current_resource.version
+ if !current_version_array.any?
+ # ! any? means it's all nil's, which means nothing is installed
+ false
+ elsif !new_version_array.any?
+ true # remove any version of all packages
+ elsif have_any_matching_version?
true # remove the version we have
else
false # we don't have the version we want to remove
end
end
+ # @todo: ability to purge an array of packages
def action_purge
if removing_package?
description = @new_resource.version ? "version #{@new_resource.version} of" : ""
@@ -132,6 +172,7 @@ class Chef
end
end
+ # @todo: ability to reconfigure an array of packages
def action_reconfig
if @current_resource.version == nil then
Chef::Log.debug("#{@new_resource} is NOT installed - nothing to do")
@@ -154,6 +195,7 @@ class Chef
end
end
+ # @todo use composition rather than inheritance
def install_package(name, version)
raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :install"
end
@@ -178,6 +220,17 @@ class Chef
raise( Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reconfig" )
end
+ # this is heavily used by subclasses
+ def expand_options(options)
+ options ? " #{options}" : ""
+ end
+
+ # this is public and overridden by subclasses (rubygems package implements '>=' and '~>' operators)
+ def target_version_already_installed?(current_version, new_version)
+ new_version == current_version
+ end
+
+ # @todo: extract apt/dpkg specific preseeding to a helper class
def get_preseed_file(name, version)
resource = preseed_resource(name, version)
resource.run_action(:create)
@@ -190,6 +243,7 @@ class Chef
end
end
+ # @todo: extract apt/dpkg specific preseeding to a helper class
def preseed_resource(name, version)
# A directory in our cache to store this cookbook's preseed files in
file_cache_dir = Chef::FileCache.create_cache_path("preseed/#{@new_resource.cookbook_name}")
@@ -216,22 +270,209 @@ class Chef
remote_file
end
- def expand_options(options)
- options ? " #{options}" : ""
+ # helper method used by subclasses
+ #
+ def as_array(thing)
+ [ thing ].flatten
end
- def target_version_already_installed?
- @new_resource.version == @current_resource.version
+ private
+
+ # Returns the package names which need to be modified. If the resource was called with an array of packages
+ # then this will return an array of packages to update (may have 0 or 1 entries). If the resource was called
+ # with a non-array package_name to manage then this will return a string rather than an Array. The output
+ # of this is meant to be fed into subclass interfaces to install/upgrade packages and not all of them are
+ # Array-aware.
+ #
+ # @return [String, Array<String>] package_name(s) to actually update/install
+ def package_names_for_targets
+ package_names_for_targets = []
+ target_version_array.each_with_index do |target_version, i|
+ next if target_version.nil?
+ package_name = package_name_array[i]
+ package_names_for_targets.push(package_name)
+ end
+ multipackage? ? package_names_for_targets : package_names_for_targets[0]
end
- private
+ # Returns the package versions which need to be modified. If the resource was called with an array of packages
+ # then this will return an array of versions to update (may have 0 or 1 entries). If the resource was called
+ # with a non-array package_name to manage then this will return a string rather than an Array. The output
+ # of this is meant to be fed into subclass interfaces to install/upgrade packages and not all of them are
+ # Array-aware.
+ #
+ # @return [String, Array<String>] package version(s) to actually update/install
+ def versions_for_targets
+ versions_for_targets = []
+ target_version_array.each_with_index do |target_version, i|
+ next if target_version.nil?
+ versions_for_targets.push(target_version)
+ end
+ multipackage? ? versions_for_targets : versions_for_targets[0]
+ end
+
+ # We need to mutate @new_resource.version() for some reason and this is a helper so that we inject the right
+ # class (String or Array) into that attribute based on if we're handling an array of package names or not.
+ #
+ # @return [String, Array<String>] target_versions coerced into the correct type for back-compat
+ def versions_for_new_resource
+ if multipackage?
+ target_version_array
+ else
+ target_version_array[0]
+ end
+ end
+
+ # Return an array indexed the same as *_version_array which contains either the target version to install/upgrade to
+ # or else nil if the package is not being modified.
+ #
+ # @return [Array<String,NilClass>] array of package versions which need to be upgraded (nil = not being upgraded)
+ def target_version_array
+ @target_version_array ||=
+ begin
+ target_version_array = []
+
+ each_package do |package_name, new_version, current_version, candidate_version|
+ case action
+ when :upgrade
+
+ if !candidate_version
+ Chef::Log.debug("#{new_resource} #{package_name} has no candidate_version to upgrade to")
+ target_version_array.push(nil)
+ elsif current_version == candidate_version
+ Chef::Log.debug("#{new_resource} #{package_name} the #{candidate_version} is already installed")
+ target_version_array.push(nil)
+ else
+ Chef::Log.debug("#{new_resource} #{package_name} is out of date, will upgrade to #{candidate_version}")
+ target_version_array.push(candidate_version)
+ end
+
+ when :install
+
+ if new_version
+ if target_version_already_installed?(current_version, new_version)
+ Chef::Log.debug("#{new_resource} #{package_name} #{current_version} satisifies #{new_version} requirement")
+ target_version_array.push(nil)
+ else
+ Chef::Log.debug("#{new_resource} #{package_name} #{current_version} needs updating to #{new_version}")
+ target_version_array.push(new_version)
+ end
+ elsif current_version.nil?
+ Chef::Log.debug("#{new_resource} #{package_name} not installed, installing #{candidate_version}")
+ target_version_array.push(candidate_version)
+ else
+ Chef::Log.debug("#{new_resource} #{package_name} #{current_version} already installed")
+ target_version_array.push(nil)
+ end
+
+ else
+ # in specs please test the public interface provider.run_action(:install) instead of provider.action_install
+ raise "internal error - target_version_array in package provider does not understand this action"
+ end
+ end
+
+ target_version_array
+ end
+ end
+
+ # Check the list of current_version_array and candidate_version_array. For any of the
+ # packages if both versions are missing (uninstalled and no candidate) this will be an
+ # unsolvable error.
+ #
+ # @return [Boolean] valid candidates exist for all uninstalled packages
+ def candidates_exist_for_all_uninstalled?
+ packages_missing_candidates.empty?
+ end
+
+ # Returns array of all packages which are missing candidate versions.
+ #
+ # @return [Array<String>] names of packages missing candidates
+ def packages_missing_candidates
+ @packages_missing_candidates ||=
+ begin
+ missing = []
+ each_package do |package_name, new_version, current_version, candidate_version|
+ missing.push(package_name) if candidate_version.nil? && current_version.nil?
+ end
+ missing
+ end
+ end
+
+ # This looks for packages which have a new_version and a current_version, and they are
+ # different (a "forced change") and for which there is no candidate. This is an edge
+ # condition that candidates_exist_for_all_uninstalled? does not catch since in this case
+ # it is not uninstalled but must be installed anyway and no version exists.
+ #
+ # @return [Boolean] valid candidates exist for all uninstalled packages
+ def candidates_exist_for_all_forced_changes?
+ forced_packages_missing_candidates.empty?
+ end
+
+ # Returns an array of all forced packages which are missing candidate versions
+ #
+ # @return [Array] names of packages missing candidates
+ def forced_packages_missing_candidates
+ @forced_packages_missing_candidates ||=
+ begin
+ missing = []
+ each_package do |package_name, new_version, current_version, candidate_version|
+ next if new_version.nil? || current_version.nil?
+ if candidate_version.nil? && !target_version_already_installed?(current_version, new_version)
+ missing.push(package_name)
+ end
+ end
+ missing
+ end
+ end
+
+ # Helper to iterate over all the indexed *_array's in sync
+ #
+ # @yield [package_name, new_version, current_version, candidate_version] Description of block
+ def each_package
+ package_name_array.each_with_index do |package_name, i|
+ candidate_version = candidate_version_array[i]
+ current_version = current_version_array[i]
+ new_version = new_version_array[i]
+ yield package_name, new_version, current_version, candidate_version
+ end
+ end
+
+ # @return [Boolean] if we're doing a multipackage install or not
+ def multipackage?
+ new_resource.package_name.is_a?(Array)
+ end
+
+ # @return [Array] package_name(s) as an array
+ def package_name_array
+ [ new_resource.package_name ].flatten
+ end
+
+ # @return [Array] candidate_version(s) as an array
+ def candidate_version_array
+ [ candidate_version ].flatten
+ end
+
+ # @return [Array] current_version(s) as an array
+ def current_version_array
+ [ current_resource.version ].flatten
+ end
+
+ # @return [Array] new_version(s) as an array
+ def new_version_array
+ @new_version_array ||=
+ [ new_resource.version ].flatten.map do |v|
+ ( v.nil? || v.empty? ) ? nil : v
+ end
+ end
+ # @todo: extract apt/dpkg specific preseeding to a helper class
def template_available?(path)
- run_context.has_template_in_cookbook?(@new_resource.cookbook_name, path)
+ run_context.has_template_in_cookbook?(new_resource.cookbook_name, path)
end
+ # @todo: extract apt/dpkg specific preseeding to a helper class
def cookbook_file_available?(path)
- run_context.has_cookbook_file_in_cookbook?(@new_resource.cookbook_name, path)
+ run_context.has_cookbook_file_in_cookbook?(new_resource.cookbook_name, path)
end
end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index fd132c817c..c960806e8f 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -51,54 +51,85 @@ class Chef
end
def check_package_state(package)
- Chef::Log.debug("#{@new_resource} checking package status for #{package}")
- installed = false
-
- shell_out!("apt-cache#{expand_options(default_release_options)} policy #{package}", :timeout => @new_resource.timeout).stdout.each_line do |line|
- case line
- when /^\s{2}Installed: (.+)$/
- installed_version = $1
- if installed_version == '(none)'
- Chef::Log.debug("#{@new_resource} current version is nil")
- @current_resource.version(nil)
- else
- Chef::Log.debug("#{@new_resource} current version is #{installed_version}")
- @current_resource.version(installed_version)
- installed = true
- end
- when /^\s{2}Candidate: (.+)$/
- candidate_version = $1
- if candidate_version == '(none)'
- # This may not be an appropriate assumption, but it shouldn't break anything that already worked -- btm
- @is_virtual_package = true
- showpkg = shell_out!("apt-cache showpkg #{package}", :timeout => @new_resource.timeout).stdout
- providers = Hash.new
- # Returns all lines after 'Reverse Provides:'
- showpkg.rpartition(/Reverse Provides:\s*#{$/}/)[2].each_line do |line|
- provider, version = line.split
- providers[provider] = version
+ final_installed_version = []
+ final_candidate_version = []
+ final_installed = []
+ final_virtual = []
+
+ [package].flatten.each do |pkg|
+ installed = virtual = false
+ installed_version = candidate_version = nil
+ shell_out!("apt-cache#{expand_options(default_release_options)} policy #{pkg}", {:timeout=>900}).stdout.each_line do |line|
+ case line
+ when /^\s{2}Installed: (.+)$/
+ installed_version = $1
+ if installed_version == '(none)'
+ Chef::Log.debug("#{@new_resource} current version is nil")
+ installed_version = nil
+ else
+ Chef::Log.debug("#{@new_resource} current version is #{installed_version}")
+ installed = true
+ end
+ when /^\s{2}Candidate: (.+)$/
+ candidate_version = $1
+ if candidate_version == '(none)'
+ # This may not be an appropriate assumption, but it shouldn't break anything that already worked -- btm
+ virtual = true
+ showpkg = shell_out!("apt-cache showpkg #{package}", {:timeout => 900}).stdout
+ providers = Hash.new
+ showpkg.rpartition(/Reverse Provides: ?#{$/}/)[2].each_line do |line|
+ provider, version = line.split
+ providers[provider] = version
+ end
+ # Check if the package providing this virtual package is installed
+ num_providers = providers.length
+ raise Chef::Exceptions::Package, "#{@new_resource.package_name} has no candidate in the apt-cache" if num_providers == 0
+ # apt will only install a virtual package if there is a single providing package
+ raise Chef::Exceptions::Package, "#{@new_resource.package_name} is a virtual package provided by #{num_providers} packages, you must explicitly select one to install" if num_providers > 1
+ # Check if the package providing this virtual package is installed
+ Chef::Log.info("#{@new_resource} is a virtual package, actually acting on package[#{providers.keys.first}]")
+ installed = check_package_state(providers.keys.first)
+ else
+ Chef::Log.debug("#{@new_resource} candidate version is #{$1}")
end
- # Check if the package providing this virtual package is installed
- num_providers = providers.length
- raise Chef::Exceptions::Package, "#{@new_resource.package_name} has no candidate in the apt-cache" if num_providers == 0
- # apt will only install a virtual package if there is a single providing package
- raise Chef::Exceptions::Package, "#{@new_resource.package_name} is a virtual package provided by #{num_providers} packages, you must explicitly select one to install" if num_providers > 1
- # Check if the package providing this virtual package is installed
- Chef::Log.info("#{@new_resource} is a virtual package, actually acting on package[#{providers.keys.first}]")
- installed = check_package_state(providers.keys.first)
- else
- Chef::Log.debug("#{@new_resource} candidate version is #{$1}")
- @candidate_version = $1
end
end
+ if package.is_a?(Array)
+ final_installed_version << installed_version
+ final_candidate_version << candidate_version
+ final_installed << installed
+ final_virtual << virtual
+ else
+ final_installed_version = installed_version
+ final_candidate_version = candidate_version
+ final_installed = installed
+ final_virtual = virtual
+ end
end
-
- return installed
+ @candidate_version = final_candidate_version
+ @current_resource.version(final_installed_version)
+ @is_virtual_package = final_virtual
+
+ return final_installed.is_a?(Array) ? final_installed.any? : final_installed
end
def install_package(name, version)
- package_name = "#{name}=#{version}"
- package_name = name if @is_virtual_package
+ if name.is_a?(Array)
+ index = 0
+ package_name = name.zip(version).map do |x, y|
+ namestr = nil
+ if @is_virtual_package[index]
+ namestr = x
+ else
+ namestr = "#{x}=#{y}"
+ end
+ index += 1
+ namestr
+ end.join(' ')
+ else
+ package_name = "#{name}=#{version}"
+ package_name = name if @is_virtual_package
+ end
run_noninteractive("apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}")
end
@@ -107,12 +138,21 @@ class Chef
end
def remove_package(name, version)
- package_name = "#{name}"
+ if name.is_a?(Array)
+ package_name = name.join(' ')
+ else
+ package_name = name
+ end
run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}")
end
def purge_package(name, version)
- run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}")
+ if name.is_a?(Array)
+ package_name = name.join(' ')
+ else
+ package_name = "#{name}"
+ end
+ run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} purge #{package_name}")
end
def preseed_package(preseed_file)
@@ -121,8 +161,13 @@ class Chef
end
def reconfig_package(name, version)
+ if name.is_a?(Array)
+ package_name = name.join(' ')
+ else
+ package_name = "#{name}"
+ end
Chef::Log.info("#{@new_resource} reconfiguring")
- run_noninteractive("dpkg-reconfigure #{name}")
+ run_noninteractive("dpkg-reconfigure #{package_name}")
end
private
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index 1f33cc5a9b..ff1e346cd1 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -484,7 +484,7 @@ class Chef
def candidate_version
@candidate_version ||= begin
- if target_version_already_installed?
+ if target_version_already_installed?(@current_resource.version, @new_resource.version)
nil
elsif source_is_remote?
@gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
@@ -494,12 +494,11 @@ class Chef
end
end
- def target_version_already_installed?
- return false unless @current_resource && @current_resource.version
- return false if @current_resource.version.nil?
- return false if @new_resource.version.nil?
+ def target_version_already_installed?(current_version, new_version)
+ return false unless current_version
+ return false if new_version.nil?
- Gem::Requirement.new(@new_resource.version).satisfied_by?(Gem::Version.new(@current_resource.version))
+ Gem::Requirement.new(new_version).satisfied_by?(Gem::Version.new(current_version))
end
##
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 505f5fd6a3..c077dfb4c2 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1054,9 +1054,18 @@ class Chef
# 3) or a dependency, eg: "foo >= 1.1"
# Check if we have name or name+arch which has a priority over a dependency
- unless @yum.package_available?(@new_resource.package_name)
- # If they aren't in the installed packages they could be a dependency
- parse_dependency
+ package_name_array.each do |n|
+ unless @yum.package_available?(n)
+ # If they aren't in the installed packages they could be a dependency
+ dep = parse_dependency(n)
+ if dep
+ if @new_resource.package_name.is_a?(Array)
+ @new_resource.package_name(package_name_array - [n] + [dep])
+ else
+ @new_resource.package_name(dep)
+ end
+ end
+ end
end
# Don't overwrite an existing arch
@@ -1090,10 +1099,18 @@ class Chef
Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
- installed_version = @yum.installed_version(@new_resource.package_name, arch)
- @current_resource.version(installed_version)
-
- @candidate_version = @yum.candidate_version(@new_resource.package_name, arch)
+ installed_version = []
+ @candidate_version = []
+ package_name_array.each do |pkg|
+ installed_version << @yum.installed_version(pkg, arch)
+ @candidate_version << @yum.candidate_version(pkg, arch)
+ end
+ if installed_version.size == 1
+ @current_resource.version(installed_version[0])
+ @candidate_version = @candidate_version[0]
+ else
+ @current_resource.version(installed_version)
+ end
Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
"#{@candidate_version || "(none)"}")
@@ -1101,43 +1118,77 @@ class Chef
@current_resource
end
- def install_package(name, version)
- if @new_resource.source
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
- else
- # Work around yum not exiting with an error if a package doesn't exist for CHEF-2062
- if @yum.version_available?(name, version, arch)
+ def install_remote_package(name, version)
+ # Work around yum not exiting with an error if a package doesn't exist
+ # for CHEF-2062
+ all_avail = as_array(name).zip(as_array(version)).any? do |n, v|
+ @yum.version_available?(n, v, arch)
+ end
+ method = log_method = nil
+ methods = []
+ if all_avail
+ # More Yum fun:
+ #
+ # yum install of an old name+version will exit(1)
+ # yum install of an old name+version+arch will exit(0) for some reason
+ #
+ # Some packages can be installed multiple times like the kernel
+ as_array(name).zip(as_array(version)).each do |n, v|
method = "install"
log_method = "installing"
-
- # More Yum fun:
- #
- # yum install of an old name+version will exit(1)
- # yum install of an old name+version+arch will exit(0) for some reason
- #
- # Some packages can be installed multiple times like the kernel
- unless @yum.allow_multi_install.include?(name)
- if RPMVersion.parse(@current_resource.version) > RPMVersion.parse(version)
- # Unless they want this...
+ idx = package_name_array.index(n)
+ unless @yum.allow_multi_install.include?(n)
+ if RPMVersion.parse(current_version_array[idx]) > RPMVersion.parse(v)
+ # We allow downgrading only in the evenit of single-package
+ # rules where the user explicitly allowed it
if allow_downgrade
method = "downgrade"
log_method = "downgrading"
else
# we bail like yum when the package is older
raise Chef::Exceptions::Package, "Installed package #{name}-#{@current_resource.version} is newer " +
- "than candidate package #{name}-#{version}"
+ "than candidate package #{n}-#{v}"
end
end
end
+ # methods don't count for packages we won't be touching
+ next if RPMVersion.parse(current_version_array[idx]) == RPMVersion.parse(v)
+ methods << method
+ end
- repo = @yum.package_repository(name, version, arch)
- Chef::Log.info("#{@new_resource} #{log_method} #{name}-#{version}#{yum_arch} from #{repo} repository")
+ # We could split this up into two commands if we wanted to, but
+ # for now, just don't support this.
+ if methods.uniq.length > 1
+ raise Chef::Exceptions::Package, "Multipackage rule #{name} has a mix of upgrade and downgrade packages. Cannot proceed."
+ end
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{name}-#{version}#{yum_arch}")
- else
- raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
- "and release? (version-release, e.g. 1.84-10.fc6)"
+ repos = []
+ pkg_string_bits = []
+ index = 0
+ as_array(name).zip(as_array(version)).each do |n, v|
+ s = ''
+ unless v == current_version_array[index]
+ s = "#{n}-#{v}#{yum_arch}"
+ repo = @yum.package_repository(n, v, arch)
+ repos << "#{s} from #{repo} repository"
+ pkg_string_bits << s
+ end
+ index += 1
end
+ pkg_string = pkg_string_bits.join(' ')
+ Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
+ else
+ raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
+ "and release? (version-release, e.g. 1.84-10.fc6)"
+ end
+ end
+
+ def install_package(name, version)
+ if @new_resource.source
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
+ else
+ install_remote_package(name, version)
end
if flush_cache[:after]
@@ -1156,10 +1207,11 @@ class Chef
# Hacky - better overall solution? Custom compare in Package provider?
def action_upgrade
# Could be uninstalled or have no candidate
- if @current_resource.version.nil? || candidate_version.nil?
+ if @current_resource.version.nil? || !candidate_version_array.any?
super
- # Ensure the candidate is newer
- elsif RPMVersion.parse(candidate_version) > RPMVersion.parse(@current_resource.version)
+ elsif candidate_version_array.zip(current_version_array).any? do |c, i|
+ RPMVersion.parse(c) > RPMVersion.parse(i)
+ end
super
else
Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
@@ -1172,10 +1224,13 @@ class Chef
def remove_package(name, version)
if version
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}-#{version}#{yum_arch}")
+ remove_str = as_array(name).zip(as_array(version)).map do |x|
+ "#{x.join('-')}#{yum_arch}"
+ end.join(' ')
else
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}#{yum_arch}")
+ remove_str = as_array(name).map { |n| "#{n}#{yum_arch}" }.join(' ')
end
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
if flush_cache[:after]
@yum.reload
@@ -1218,9 +1273,9 @@ class Chef
# matching them up with an actual package so the standard resource handling can apply.
#
# There is currently no support for filename matching.
- def parse_dependency
+ def parse_dependency(name)
# Transform the package_name into a requirement
- yum_require = RPMRequire.parse(@new_resource.package_name)
+ yum_require = RPMRequire.parse(name)
# and gather all the packages that have a Provides feature satisfying the requirement.
# It could be multiple be we can only manage one
packages = @yum.packages_from_require(yum_require)
@@ -1254,7 +1309,7 @@ class Chef
"specific version.")
end
- @new_resource.package_name(new_package_name)
+ new_package_name
end
end
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index 772439b06c..f4f49b543b 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -46,7 +46,7 @@ class Chef
set_or_return(
:package_name,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String, Array ]
)
end
@@ -54,7 +54,7 @@ class Chef
set_or_return(
:version,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String, Array ]
)
end
diff --git a/spec/unit/provider/package/aix_spec.rb b/spec/unit/provider/package/aix_spec.rb
index 6908b1288d..a39ab096c7 100644
--- a/spec/unit/provider/package/aix_spec.rb
+++ b/spec/unit/provider/package/aix_spec.rb
@@ -54,8 +54,8 @@ describe Chef::Provider::Package::Aix do
it "should raise an exception if a source is supplied but not found" do
allow(@provider).to receive(:popen4).and_return(@status)
allow(::File).to receive(:exists?).and_return(false)
- @provider.define_resource_requirements
@provider.load_current_resource
+ @provider.define_resource_requirements
expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Package)
end
diff --git a/spec/unit/provider/package/apt_spec.rb b/spec/unit/provider/package/apt_spec.rb
index e53fdc3f27..acf0707bbf 100644
--- a/spec/unit/provider/package/apt_spec.rb
+++ b/spec/unit/provider/package/apt_spec.rb
@@ -198,6 +198,11 @@ mpg123 1.12.1-0ubuntu1
it "raises an exception if a source is specified (CHEF-5113)" do
@new_resource.source "pluto"
+ expect(@provider).to receive(:shell_out!).with(
+ "apt-cache policy #{@new_resource.package_name}",
+ :timeout => @timeout
+ ).and_return(@shell_out)
+ @provider.load_current_resource
@provider.define_resource_requirements
expect(@provider).to receive(:shell_out!).with("apt-cache policy irssi", {:timeout=>900}).and_return(@shell_out)
expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
@@ -307,8 +312,7 @@ mpg123 1.12.1-0ubuntu1
end
it "should get the full path to the preseed response file" do
- expect(@provider).to receive(:get_preseed_file).with("irssi", "0.8.12-7").and_return("/tmp/irssi-0.8.12-7.seed")
- file = @provider.get_preseed_file("irssi", "0.8.12-7")
+ file = "/tmp/irssi-0.8.12-7.seed"
expect(@provider).to receive(:shell_out!).with(
"debconf-set-selections /tmp/irssi-0.8.12-7.seed",
diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb
index fdd9e50c8e..154809f88c 100644
--- a/spec/unit/provider/package/dpkg_spec.rb
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -88,8 +88,8 @@ describe Chef::Provider::Package::Dpkg do
it "should raise an exception if the source is not set but we are installing" do
@new_resource = Chef::Resource::Package.new("wget")
@provider.new_resource = @new_resource
- @provider.define_resource_requirements
@provider.load_current_resource
+ @provider.define_resource_requirements
expect { @provider.run_action(:install)}.to raise_error(Chef::Exceptions::Package)
end
diff --git a/spec/unit/provider/package/ips_spec.rb b/spec/unit/provider/package/ips_spec.rb
index 4e0afc46e9..342ac4c040 100644
--- a/spec/unit/provider/package/ips_spec.rb
+++ b/spec/unit/provider/package/ips_spec.rb
@@ -190,9 +190,8 @@ REMOTE
expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local)
expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote)
- @provider.load_current_resource
expect(@provider).to receive(:install_package).exactly(0).times
- @provider.action_install
+ @provider.run_action(:install)
end
context "when accept_license is true" do
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
index b4960b2af3..b17c216ddd 100644
--- a/spec/unit/provider/package/rubygems_spec.rb
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -371,6 +371,8 @@ describe Chef::Provider::Package::Rubygems do
# We choose detect omnibus via RbConfig::CONFIG['bindir'] in Chef::Provider::Package::Rubygems.new
allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/usr/bin/ruby")
+ # Rubygems uses this interally
+ allow(RbConfig::CONFIG).to receive(:[]).with('arch').and_call_original
@provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
end
@@ -379,7 +381,7 @@ describe Chef::Provider::Package::Rubygems do
it "target_version_already_installed? should return false so that we can search for candidates" do
@provider.load_current_resource
- expect(@provider.target_version_already_installed?).to be_falsey
+ expect(@provider.target_version_already_installed?(@provider.current_resource.version, @new_resource.version)).to be_falsey
end
end
@@ -469,6 +471,8 @@ describe Chef::Provider::Package::Rubygems do
it "determines the candidate version by querying the remote gem servers" do
@new_resource.source('http://mygems.example.com')
+ @provider.load_current_resource
+ @provider.current_resource.version('0.0.1')
version = Gem::Version.new(@spec_version)
expect(@provider.gem_env).to receive(:candidate_version_from_remote).
with(Gem::Dependency.new('rspec-core', @spec_version), "http://mygems.example.com").
@@ -478,8 +482,9 @@ describe Chef::Provider::Package::Rubygems do
it "parses the gem's specification if the requested source is a file" do
@new_resource.package_name('chef-integration-test')
- @new_resource.version('>= 0')
@new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+ @new_resource.version('>= 0')
+ @provider.load_current_resource
expect(@provider.candidate_version).to eq('0.1.0')
end
@@ -496,20 +501,23 @@ describe Chef::Provider::Package::Rubygems do
describe "in the current gem environment" do
it "installs the gem via the gems api when no explicit options are used" do
expect(@provider.gem_env).to receive(:install).with(@gem_dep, :sources => nil)
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
it "installs the gem via the gems api when a remote source is provided" do
@new_resource.source('http://gems.example.org')
sources = ['http://gems.example.org']
expect(@provider.gem_env).to receive(:install).with(@gem_dep, :sources => sources)
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
it "installs the gem from file via the gems api when no explicit options are used" do
@new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
expect(@provider.gem_env).to receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
it "installs the gem from file via the gems api when the package is a path and the source is nil" do
@@ -518,7 +526,8 @@ describe Chef::Provider::Package::Rubygems do
@provider.current_resource = @current_resource
expect(@new_resource.source).to eq(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
expect(@provider.gem_env).to receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
# this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem
@@ -526,28 +535,35 @@ describe Chef::Provider::Package::Rubygems do
allow(::File).to receive(:exists?).and_return(true)
@new_resource.package_name('rspec-core')
expect(@provider.gem_env).to receive(:install).with(@gem_dep, :sources => nil)
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
it "installs the gem by shelling out when options are provided as a String" do
@new_resource.options('-i /alt/install/location')
expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location"
expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
- it "installs the gem by shelling out when options are provided but no version is given" do
- @new_resource.options('-i /alt/install/location')
- @new_resource.version('')
- expected ="gem install \"rspec-core\" -q --no-rdoc --no-ri -i /alt/install/location"
- expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
- expect(@provider.action_install).to be_truthy
+ context "when no version is given" do
+ let(:target_version) { nil }
+
+ it "installs the gem by shelling out when options are provided but no version is given" do
+ @new_resource.options('-i /alt/install/location')
+ expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@provider.candidate_version}\" -i /alt/install/location"
+ expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
+ end
end
it "installs the gem via the gems api when options are given as a Hash" do
@new_resource.options(:install_dir => '/alt/install/location')
expect(@provider.gem_env).to receive(:install).with(@gem_dep, :sources => nil, :install_dir => '/alt/install/location')
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
describe "at a specific version" do
@@ -557,24 +573,25 @@ describe Chef::Provider::Package::Rubygems do
it "installs the gem via the gems api" do
expect(@provider.gem_env).to receive(:install).with(@gem_dep, :sources => nil)
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
end
describe "at version specified with comparison operator" do
it "skips install if current version satisifies requested version" do
- allow(@current_resource).to receive(:version).and_return("2.3.3")
- allow(@new_resource).to receive(:version).and_return(">=2.3.0")
+ @current_resource.version("2.3.3")
+ @new_resource.version(">=2.3.0")
expect(@provider.gem_env).not_to receive(:install)
- @provider.action_install
+ @provider.run_action(:install)
end
it "allows user to specify gem version with fuzzy operator" do
- allow(@current_resource).to receive(:version).and_return("2.3.3")
- allow(@new_resource).to receive(:version).and_return("~>2.3.0")
+ @current_resource.version("2.3.3")
+ @new_resource.version("~>2.3.0")
expect(@provider.gem_env).not_to receive(:install)
- @provider.action_install
+ @provider.run_action(:install)
end
end
end
@@ -583,7 +600,8 @@ describe Chef::Provider::Package::Rubygems do
it "installs the gem by shelling out to gem install" do
@new_resource.gem_binary('/usr/weird/bin/gem')
expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil)
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
it "installs the gem from file by shelling out to gem install" do
@@ -591,7 +609,8 @@ describe Chef::Provider::Package::Rubygems do
@new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
@new_resource.version('>= 0')
expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil)
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do
@@ -602,7 +621,8 @@ describe Chef::Provider::Package::Rubygems do
@new_resource.version('>= 0')
expect(@new_resource.source).to eq(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil)
- expect(@provider.action_install).to be_truthy
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
end
end
diff --git a/spec/unit/provider/package/solaris_spec.rb b/spec/unit/provider/package/solaris_spec.rb
index 8438202576..332fa9db1a 100644
--- a/spec/unit/provider/package/solaris_spec.rb
+++ b/spec/unit/provider/package/solaris_spec.rb
@@ -64,8 +64,8 @@ PKGINFO
it "should raise an exception if a source is supplied but not found" do
allow(@provider).to receive(:popen4).and_return(@status)
allow(::File).to receive(:exists?).and_return(false)
- @provider.define_resource_requirements
@provider.load_current_resource
+ @provider.define_resource_requirements
expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Package)
end
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
index 0d2a44f3ae..8b4c71ee0a 100644
--- a/spec/unit/provider/package/yum_spec.rb
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -337,9 +337,9 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install emacs-1.0"
+ "yum -d0 -e0 -y install cups-1.2.4-11.19.el5"
)
- @provider.install_package("emacs", "1.0")
+ @provider.install_package("cups", "1.2.4-11.19.el5")
end
it "should run yum localinstall if given a path to an rpm" do
@@ -366,14 +366,14 @@ describe Chef::Provider::Package::Yum do
allow(@new_resource).to receive(:arch).and_return("i386")
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install emacs-21.4-20.el5.i386"
+ "yum -d0 -e0 -y install cups-1.2.4-11.19.el5.i386"
)
- @provider.install_package("emacs", "21.4-20.el5")
+ @provider.install_package("cups", "1.2.4-11.19.el5")
end
it "installs the package with the options given in the resource" do
@provider.load_current_resource
- @provider.candidate_version = '11'
+ allow(@provider).to receive(:candidate_version).and_return('11')
allow(@new_resource).to receive(:options).and_return("--disablerepo epmd")
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
@@ -467,10 +467,10 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install emacs-1.0"
+ "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
)
expect(@yum_cache).to receive(:reload).once
- @provider.install_package("emacs", "1.0")
+ @provider.install_package("cups", "1.2.4-11.15.el5")
end
it "should run yum install then not flush the cache if :after is false" do
@@ -478,17 +478,17 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install emacs-1.0"
+ "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
)
expect(@yum_cache).not_to receive(:reload)
- @provider.install_package("emacs", "1.0")
+ @provider.install_package("cups", "1.2.4-11.15.el5")
end
end
describe "when upgrading a package" do
it "should run yum install if the package is installed and a version is given" do
@provider.load_current_resource
- @provider.candidate_version = '11'
+ allow(@provider).to receive(:candidate_version).and_return('11')
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
"yum -d0 -e0 -y install cups-11"
@@ -499,7 +499,7 @@ describe Chef::Provider::Package::Yum do
it "should run yum install if the package is not installed" do
@provider.load_current_resource
@current_resource = Chef::Resource::Package.new('cups')
- @provider.candidate_version = '11'
+ allow(@provider).to receive(:candidate_version).and_return('11')
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
"yum -d0 -e0 -y install cups-11"
@@ -528,42 +528,41 @@ describe Chef::Provider::Package::Yum do
# Test our little workaround, some crossover into Chef::Provider::Package territory
it "should call action_upgrade in the parent if the current resource version is nil" do
allow(@yum_cache).to receive(:installed_version).and_return(nil)
- @provider.load_current_resource
@current_resource = Chef::Resource::Package.new('cups')
- @provider.candidate_version = '11'
+ allow(@provider).to receive(:candidate_version).and_return('11')
expect(@provider).to receive(:upgrade_package).with(
"cups",
"11"
)
- @provider.action_upgrade
+ @provider.run_action(:upgrade)
end
it "should call action_upgrade in the parent if the candidate version is nil" do
@provider.load_current_resource
@current_resource = Chef::Resource::Package.new('cups')
- @provider.candidate_version = nil
+ allow(@provider).to receive(:candidate_version).and_return(nil)
expect(@provider).not_to receive(:upgrade_package)
- @provider.action_upgrade
+ @provider.run_action(:upgrade)
end
it "should call action_upgrade in the parent if the candidate is newer" do
@provider.load_current_resource
@current_resource = Chef::Resource::Package.new('cups')
- @provider.candidate_version = '11'
+ allow(@provider).to receive(:candidate_version).and_return('11')
expect(@provider).to receive(:upgrade_package).with(
"cups",
"11"
)
- @provider.action_upgrade
+ @provider.run_action(:upgrade)
end
it "should not call action_upgrade in the parent if the candidate is older" do
allow(@yum_cache).to receive(:installed_version).and_return("12")
@provider.load_current_resource
@current_resource = Chef::Resource::Package.new('cups')
- @provider.candidate_version = '11'
+ allow(@provider).to receive(:candidate_version).and_return('11')
expect(@provider).not_to receive(:upgrade_package)
- @provider.action_upgrade
+ @provider.run_action(:upgrade)
end
end
@@ -1861,3 +1860,103 @@ EOF
end
end
+
+describe "Chef::Provider::Package::Yum - Multi" do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new(['cups', 'vim'])
+ @status = double("Status", :exitstatus => 0)
+ @yum_cache = double(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => 'XXXX',
+ :candidate_version => 'YYYY',
+ :package_available? => true,
+ :version_available? => true,
+ :allow_multi_install => [ 'kernel' ],
+ :package_repository => 'base',
+ :disable_extra_repo_control => true
+ )
+ allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @pid = double("PID")
+ end
+
+ describe "when loading the current system state" do
+ it "should create a current resource with the name of the new_resource" do
+ @provider.load_current_resource
+ expect(@provider.current_resource.name).to eq(['cups', 'vim'])
+ end
+
+ it "should set the current resources package name to the new resources package name" do
+ @provider.load_current_resource
+ expect(@provider.current_resource.package_name).to eq(['cups', 'vim'])
+ end
+
+ it "should set the installed version to nil on the current resource if no installed package" do
+ allow(@yum_cache).to receive(:installed_version).and_return(nil)
+ @provider.load_current_resource
+ expect(@provider.current_resource.version).to eq([nil, nil])
+ end
+
+ it "should set the installed version if yum has one" do
+ allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
+ allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('1.0')
+ allow(@yum_cache).to receive(:candidate_version).with('cups', nil).and_return('1.2.4-11.18.el5_2.3')
+ allow(@yum_cache).to receive(:candidate_version).with('vim', nil).and_return('1.5')
+ @provider.load_current_resource
+ expect(@provider.current_resource.version).to eq(['1.2.4-11.18.el5', '1.0'])
+ end
+
+ it "should set the candidate version if yum info has one" do
+ allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
+ allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('1.0')
+ allow(@yum_cache).to receive(:candidate_version).with('cups', nil).and_return('1.2.4-11.18.el5_2.3')
+ allow(@yum_cache).to receive(:candidate_version).with('vim', nil).and_return('1.5')
+ @provider.load_current_resource
+ expect(@provider.candidate_version).to eql(['1.2.4-11.18.el5_2.3', '1.5'])
+ end
+
+ it "should return the current resouce" do
+ expect(@provider.load_current_resource).to eql(@provider.current_resource)
+ end
+ end
+
+ describe "when installing a package" do
+ it "should run yum install with the package name and version" do
+ @provider.load_current_resource
+ allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
+ allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
+ allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
+ expect(@provider).to receive(:yum_command).with(
+ "yum -d0 -e0 -y install cups-1.2.4-11.19.el5 vim-1.0"
+ )
+ @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
+ end
+
+ it "should run yum install with the package name, version and arch" do
+ @provider.load_current_resource
+ allow(@new_resource).to receive(:arch).and_return("i386")
+ allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
+ expect(@provider).to receive(:yum_command).with(
+ "yum -d0 -e0 -y install cups-1.2.4-11.19.el5.i386 vim-1.0.i386"
+ )
+ @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", "1.0"])
+ end
+
+ it "installs the package with the options given in the resource" do
+ @provider.load_current_resource
+ allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
+ allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
+ allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
+ expect(@provider).to receive(:yum_command).with(
+ "yum -d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0"
+ )
+ allow(@new_resource).to receive(:options).and_return("--disablerepo epmd")
+ @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
+ end
+ end
+end
diff --git a/spec/unit/provider/package_spec.rb b/spec/unit/provider/package_spec.rb
index 2aeaf717e6..6a0cb695b0 100644
--- a/spec/unit/provider/package_spec.rb
+++ b/spec/unit/provider/package_spec.rb
@@ -152,7 +152,7 @@ describe Chef::Provider::Package do
it "should print the word 'uninstalled' if there was no original version" do
allow(@current_resource).to receive(:version).and_return(nil)
- expect(Chef::Log).to receive(:info).with("package[emacs] upgraded from uninstalled to 1.0")
+ expect(Chef::Log).to receive(:info).with("package[emacs] upgraded emacs to 1.0")
@provider.run_action(:upgrade)
expect(@new_resource).to be_updated_by_last_action
end
@@ -425,3 +425,277 @@ describe Chef::Provider::Package do
end
end
+
+describe "Chef::Provider::Package - Multi" do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new(['emacs', 'vi'])
+ @current_resource = Chef::Resource::Package.new(['emacs', 'vi'])
+ @provider = Chef::Provider::Package.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @provider.candidate_version = ['1.0', '6.2']
+ end
+
+ describe "when installing multiple packages" do
+ before(:each) do
+ @provider.current_resource = @current_resource
+ allow(@provider).to receive(:install_package).and_return(true)
+ end
+
+ it "installs the candidate versions when none are installed" do
+ expect(@provider).to receive(:install_package).with(
+ ["emacs", "vi"],
+ ["1.0", "6.2"]
+ ).and_return(true)
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated
+ end
+
+ it "installs the candidate versions when some are installed" do
+ expect(@provider).to receive(:install_package).with(
+ [ 'vi' ],
+ [ '6.2' ]
+ ).and_return(true)
+ @current_resource.version(['1.0', nil])
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated
+ end
+
+ it "installs the specified version when some are out of date" do
+ @current_resource.version(['1.0', '6.2'])
+ @new_resource.version(['1.0', '6.1'])
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated
+ end
+
+ it "does not install any version if all are installed at the right version" do
+ @current_resource.version(['1.0', '6.2'])
+ @new_resource.version(['1.0', '6.2'])
+ @provider.run_action(:install)
+ expect(@new_resource).not_to be_updated_by_last_action
+ end
+
+ it "does not install any version if all are installed, and no version was specified" do
+ @current_resource.version(['1.0', '6.2'])
+ @provider.run_action(:install)
+ expect(@new_resource).not_to be_updated_by_last_action
+ end
+
+ it "raises an exception if both are not installed and no caondidates are available" do
+ @current_resource.version([nil, nil])
+ @provider.candidate_version = [nil, nil]
+ expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ it "raises an exception if one is not installed and no candidates are available" do
+ @current_resource.version(['1.0', nil])
+ @provider.candidate_version = ['1.0', nil]
+ expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ it "does not raise an exception if the packages are installed or have a candidate" do
+ @current_resource.version(['1.0', nil])
+ @provider.candidate_version = [nil, '6.2']
+ expect { @provider.run_action(:install) }.not_to raise_error
+ end
+
+ it "raises an exception if an explicit version is asked for, an old version is installed, but no candidate" do
+ @new_resource.version ['1.0', '6.2']
+ @current_resource.version(['1.0', '6.1'])
+ @provider.candidate_version = ['1.0', nil]
+ expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ it "does not raise an exception if an explicit version is asked for, and is installed, but no candidate" do
+ @new_resource.version ['1.0', '6.2']
+ @current_resource.version(['1.0', '6.2'])
+ @provider.candidate_version = ['1.0', nil]
+ expect { @provider.run_action(:install) }.not_to raise_error
+ end
+
+ it "raise an exception if an explicit version is asked for, and is not installed, and no candidate" do
+ @new_resource.version ['1.0', '6.2']
+ @current_resource.version(['1.0', nil])
+ @provider.candidate_version = ['1.0', nil]
+ expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ it "does not raise an exception if an explicit version is asked for, and is not installed, and there is a candidate" do
+ @new_resource.version ['1.0', '6.2']
+ @current_resource.version(['1.0', nil])
+ @provider.candidate_version = ['1.0', '6.2']
+ expect { @provider.run_action(:install) }.not_to raise_error
+ end
+ end
+
+ describe "when upgrading multiple packages" do
+ before(:each) do
+ @provider.current_resource = @current_resource
+ allow(@provider).to receive(:upgrade_package).and_return(true)
+ end
+
+ it "should upgrade the package if the current versions are not the candidate version" do
+ @current_resource.version ['0.9', '6.1']
+ expect(@provider).to receive(:upgrade_package).with(
+ @new_resource.name,
+ @provider.candidate_version
+ ).and_return(true)
+ @provider.run_action(:upgrade)
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should upgrade the package if some of current versions are not the candidate versions" do
+ @current_resource.version ['1.0', '6.1']
+ expect(@provider).to receive(:upgrade_package).with(
+ ["vi"],
+ ["6.2"]
+ ).and_return(true)
+ @provider.run_action(:upgrade)
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should not install the package if the current versions are the candidate version" do
+ @current_resource.version ['1.0', '6.2']
+ expect(@provider).not_to receive(:upgrade_package)
+ @provider.run_action(:upgrade)
+ expect(@new_resource).not_to be_updated_by_last_action
+ end
+
+ it "should raise an exception if both are not installed and no caondidates are available" do
+ @current_resource.version([nil, nil])
+ @provider.candidate_version = [nil, nil]
+ expect { @provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should raise an exception if one is not installed and no candidates are available" do
+ @current_resource.version(['1.0', nil])
+ @provider.candidate_version = ['1.0', nil]
+ expect { @provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should not raise an exception if the packages are installed or have a candidate" do
+ @current_resource.version(['1.0', nil])
+ @provider.candidate_version = [nil, '6.2']
+ expect { @provider.run_action(:upgrade) }.not_to raise_error
+ end
+
+ it "should not raise an exception if the packages are installed or have a candidate" do
+ @current_resource.version(['1.0', nil])
+ @provider.candidate_version = [nil, '6.2']
+ expect { @provider.run_action(:upgrade) }.not_to raise_error
+ end
+ end
+
+ describe "When removing multiple packages " do
+ before(:each) do
+ allow(@provider).to receive(:remove_package).and_return(true)
+ @current_resource.version ['1.0', '6.2']
+ end
+
+ it "should remove the packages if all are installed" do
+ expect(@provider).to be_removing_package
+ expect(@provider).to receive(:remove_package).with(['emacs', 'vi'], nil)
+ @provider.run_action(:remove)
+ expect(@new_resource).to be_updated
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should remove the packages if some are installed" do
+ @current_resource.version ['1.0', nil]
+ expect(@provider).to be_removing_package
+ expect(@provider).to receive(:remove_package).with(['emacs', 'vi'], nil)
+ @provider.run_action(:remove)
+ expect(@new_resource).to be_updated
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should remove the packages at a specific version if they are installed at that version" do
+ @new_resource.version ['1.0', '6.2']
+ expect(@provider).to be_removing_package
+ expect(@provider).to receive(:remove_package).with(['emacs', 'vi'], ['1.0', '6.2'])
+ @provider.run_action(:remove)
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should remove the packages at a specific version any are is installed at that version" do
+ @new_resource.version ['0.5', '6.2']
+ expect(@provider).to be_removing_package
+ expect(@provider).to receive(:remove_package).with(['emacs', 'vi'], ['0.5', '6.2'])
+ @provider.run_action(:remove)
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should not remove the packages at a specific version if they are not installed at that version" do
+ @new_resource.version ['0.5', '6.0']
+ expect(@provider).not_to be_removing_package
+ expect(@provider).not_to receive(:remove_package)
+ @provider.run_action(:remove)
+ expect(@new_resource).not_to be_updated_by_last_action
+ end
+
+ it "should not remove the packages if they are not installed" do
+ expect(@provider).not_to receive(:remove_package)
+ allow(@current_resource).to receive(:version).and_return(nil)
+ @provider.run_action(:remove)
+ expect(@new_resource).not_to be_updated_by_last_action
+ end
+
+ end
+
+ describe "When purging multiple packages " do
+ before(:each) do
+ allow(@provider).to receive(:purge_package).and_return(true)
+ @current_resource.version ['1.0', '6.2']
+ end
+
+ it "should purge the packages if all are installed" do
+ expect(@provider).to be_removing_package
+ expect(@provider).to receive(:purge_package).with(['emacs', 'vi'], nil)
+ @provider.run_action(:purge)
+ expect(@new_resource).to be_updated
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should purge the packages if some are installed" do
+ @current_resource.version ['1.0', nil]
+ expect(@provider).to be_removing_package
+ expect(@provider).to receive(:purge_package).with(['emacs', 'vi'], nil)
+ @provider.run_action(:purge)
+ expect(@new_resource).to be_updated
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should purge the packages at a specific version if they are installed at that version" do
+ @new_resource.version ['1.0', '6.2']
+ expect(@provider).to be_removing_package
+ expect(@provider).to receive(:purge_package).with(['emacs', 'vi'], ['1.0', '6.2'])
+ @provider.run_action(:purge)
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should purge the packages at a specific version any are is installed at that version" do
+ @new_resource.version ['0.5', '6.2']
+ expect(@provider).to be_removing_package
+ expect(@provider).to receive(:purge_package).with(['emacs', 'vi'], ['0.5', '6.2'])
+ @provider.run_action(:purge)
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "should not purge the packages at a specific version if they are not installed at that version" do
+ @new_resource.version ['0.5', '6.0']
+ expect(@provider).not_to be_removing_package
+ expect(@provider).not_to receive(:purge_package)
+ @provider.run_action(:purge)
+ expect(@new_resource).not_to be_updated_by_last_action
+ end
+
+ it "should not purge the packages if they are not installed" do
+ expect(@provider).not_to receive(:purge_package)
+ allow(@current_resource).to receive(:version).and_return(nil)
+ @provider.run_action(:purge)
+ expect(@new_resource).not_to be_updated_by_last_action
+ end
+ end
+end
diff --git a/spec/unit/provider/package_spec.rbe b/spec/unit/provider/package_spec.rbe
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/spec/unit/provider/package_spec.rbe