summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Duffield <tom@chef.io>2017-02-08 09:18:51 -0600
committerTom Duffield <tom@chef.io>2017-02-08 14:14:23 -0600
commitd6ad9a7d2e53f6015511c9fae3765a83d469403c (patch)
tree20dfb9c71bcf898b2d2a0f59aa8a4cd0c5d27606
parent88de5e1dfbfa07da1f666a5ec0ba5c39a96a4122 (diff)
downloadchef-tduffield/COOL-612/yum-provider-arch.tar.gz
Add mutlipackage_api support to yum_package Providertduffield/COOL-612/yum-provider-arch
An initial refactor to add multipackage_api support to the yum_package provider. There probably needs to be an additional refactor to make this provider look a little bit more like the DNF provider, but this should buy us some time and address some bugs. Signed-off-by: Tom Duffield <tom@chef.io>
-rw-r--r--lib/chef/provider/package/yum.rb551
-rw-r--r--spec/unit/provider/package/yum_spec.rb105
2 files changed, 348 insertions, 308 deletions
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 8b7d7a5083..22ba5cb50a 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1,6 +1,6 @@
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright 2008-2017, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,12 +27,17 @@ class Chef
class Provider
class Package
class Yum < Chef::Provider::Package
+ include Chef::Mixin::GetSourceFromPackage
provides :package, platform_family: %w{rhel fedora}
provides :yum_package, os: "linux"
- include Chef::Mixin::GetSourceFromPackage
+ # Multipackage API
+ allow_nils
+ use_multipackage_api
+ use_package_name_for_source
+ # Overload the Package provider to keep track of the YumCache
def initialize(new_resource, run_context)
super
@@ -40,89 +45,60 @@ class Chef
@yum.yum_binary = yum_binary
end
- def yum_binary
- @yum_binary ||=
- begin
- yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
- yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
- end
- end
-
- # Extra attributes
- #
+ # @see Chef::Provider::Package#check_resource_semantics!
+ def check_resource_semantics!
+ super
- def arch_for_name(n)
- if @new_resource.respond_to?("arch")
- @new_resource.arch
- elsif @arch
- idx = package_name_array.index(n)
- as_array(@arch)[idx]
- else
- nil
+ if !new_resource.version.nil? && package_name_array.length != new_version_array.length
+ raise Chef::Exceptions::InvalidResourceSpecification, "Please provide a version for each package. Use `nil` for default version."
end
- end
- def arch
- if @new_resource.respond_to?("arch")
- @new_resource.arch
- else
- nil
+ if !new_resource.arch.nil? && package_name_array.length != safe_arch_array.length
+ raise Chef::Exceptions::InvalidResourceSpecification, "Please provide an architecture for each package. Use `nil` for default architecture."
end
end
- def set_arch(arch)
- if @new_resource.respond_to?("arch")
- @new_resource.arch(arch)
- end
- end
+ # @see Chef::Provider#define_resource_requirements
+ def define_resource_requirements
+ super
- def flush_cache
- if @new_resource.respond_to?("flush_cache")
- @new_resource.flush_cache
- else
- { :before => false, :after => false }
+ # Ensure that the source file (if specified) is present on the file system
+ requirements.assert(:install, :upgrade, :remove, :purge) do |a|
+ a.assertion { !new_resource.source || ::File.exist?(new_resource.source) }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "assuming #{new_resource.source} would have previously been created"
end
end
- # Helpers
- #
-
- def yum_arch(arch)
- arch ? ".#{arch}" : nil
- end
+ # @see Chef::Provider#load_current_resource
+ def load_current_resource
+ @yum.reload if flush_cache[:before]
+ manage_extra_repo_control
- def yum_command(command)
- command = "#{yum_binary} #{command}"
- Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
- status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
+ if new_resource.source
+ query_source_file
+ else
+ # At this point package_name could be:
+ #
+ # 1) a package name, eg: "foo"
+ # 2) a package name.arch, eg: "foo.i386"
+ # 3) or a dependency, eg: "foo >= 1.1"
+ #
+ # In the third case, we want to convert those dependency strings into
+ # packages that we can actually install
+ convert_dependency_strings_into_packages
- # This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
- # considered fatal - meaning the rpm is still successfully installed. These issue
- # cause yum to emit a non fatal warning but still exit(1). As there's currently no
- # way to suppress this behavior and an exit(1) will break a Chef run we make an
- # effort to trap these and re-run the same install command - it will either fail a
- # second time or succeed.
- #
- # A cleaner solution would have to be done in python and better hook into
- # yum/rpm to handle exceptions as we see fit.
- if status.exitstatus == 1
- status.stdout.each_line do |l|
- # rpm-4.4.2.3 lib/psm.c line 2182
- if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
- Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
- "so running install again to verify.")
- status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
- break
- end
- end
+ # Fill out the rest of the details by querying the Yum Cache
+ query_yum_cache
end
- if status.exitstatus > 0
- command_output = "STDOUT: #{status.stdout}\nSTDERR: #{status.stderr}"
- raise Chef::Exceptions::Exec, "#{command} returned #{status.exitstatus}:\n#{command_output}"
- end
+ @current_resource = Chef::Resource::YumPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(@installed_version)
+ current_resource
end
+ # @see Chef::Provider::Package#package_locked
def package_locked(name, version)
islocked = false
locked = shell_out_with_timeout!("yum versionlock")
@@ -135,20 +111,91 @@ class Chef
return islocked
end
- # Standard Provider methods for Parent
+ #
+ # Package Action Classes
#
- def load_current_resource
- if flush_cache[:before]
- @yum.reload
+ # @see Chef::Provider::Package#install_package
+ def install_package(name, version)
+ if new_resource.source
+ yum_command("-d0 -e0 -y#{expand_options(new_resource.options)} localinstall #{new_resource.source}")
+ else
+ install_remote_package(name, version)
end
- if @new_resource.options
+ flush_cache[:after] ? @yum.reload : @yum.reload_installed
+ end
+
+ # @see Chef::Provider::Package#upgrade_package
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ # @see Chef::Provider::Package#remove_package
+ def remove_package(name, version)
+ remove_str = full_package_name(name, version).join(" ")
+ yum_command("-d0 -e0 -y#{expand_options(new_resource.options)} remove #{remove_str}")
+
+ flush_cache[:after] ? @yum.reload : @yum.reload_installed
+ end
+
+ # @see Chef::Provider::Package#purge_package
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ # @see Chef::Provider::Package#lock_package
+ def lock_package(name, version)
+ lock_str = full_package_name(name, as_array(name).map { nil }).join(" ")
+ yum_command("-d0 -e0 -y#{expand_options(new_resource.options)} versionlock add #{lock_str}")
+ end
+
+ # @see Chef::Provider::Package#unlock_package
+ def unlock_package(name, version)
+ unlock_str = full_package_name(name, as_array(name).map { nil }).join(" ")
+ yum_command("-d0 -e0 -y#{expand_options(new_resource.options)} versionlock delete #{unlock_str}")
+ end
+
+ # Keep upgrades from trying to install an older candidate version. Can happen when a new
+ # version is installed then removed from a repository, now the older available version
+ # shows up as a viable install candidate.
+ #
+ # Can be done in upgrade_package but an upgraded from->to log message slips out
+ #
+ # 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_array.any?
+ super
+ 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")
+ end
+ end
+
+ private
+
+ #
+ # System Level Yum Operations
+ #
+
+ def yum_binary
+ @yum_binary ||=
+ begin
+ yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
+ yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
+ end
+ end
+
+ # Enable or disable YumCache extra_repo_control
+ def manage_extra_repo_control
+ if new_resource.options
repo_control = []
- @new_resource.options.split.each do |opt|
- if opt =~ %r{--(enable|disable)repo=.+}
- repo_control << opt
- end
+ new_resource.options.split.each do |opt|
+ repo_control << opt if opt =~ %r{--(enable|disable)repo=.+}
end
if repo_control.size > 0
@@ -159,105 +206,99 @@ class Chef
else
@yum.disable_extra_repo_control
end
+ end
- # At this point package_name could be:
- #
- # 1) a package name, eg: "foo"
- # 2) a package name.arch, eg: "foo.i386"
- # 3) or a dependency, eg: "foo >= 1.1"
+ # Query the Yum cache for information about potential packages
+ def query_yum_cache
+ installed_versions = []
+ candidate_versions = []
- # Check if we have name or name+arch which has a priority over a dependency
- package_name_array.each_with_index do |n, index|
- unless @yum.package_available?(n)
- # If they aren't in the installed packages they could be a dependency
- dep = parse_dependency(n, new_version_array[index])
- if dep
- if @new_resource.package_name.is_a?(Array)
- @new_resource.package_name(package_name_array - [n] + [dep.first])
- @new_resource.version(new_version_array - [new_version_array[index]] + [dep.last]) if dep.last
- else
- @new_resource.package_name(dep.first)
- @new_resource.version(dep.last) if dep.last
- end
- end
- end
- end
+ package_name_array.each_with_index do |n, idx|
+ pkg_name, eval_pkg_arch = parse_arch(n)
- @current_resource = Chef::Resource::YumPackage.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ # Defer to the arch property for the desired package architecture
+ pkg_arch = safe_arch_array[idx] || eval_pkg_arch
+ set_package_name(idx, pkg_name)
+ set_package_arch(idx, pkg_arch)
- installed_version = []
- @candidate_version = []
- @arch = []
- if @new_resource.source
- unless ::File.exists?(@new_resource.source)
- raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- end
+ Chef::Log.debug("#{new_resource} checking yum info for #{yum_syntax(n, nil, pkg_arch)}")
+ installed_versions << iv = @yum.installed_version(pkg_name, pkg_arch)
+ candidate_versions << cv = @yum.candidate_version(pkg_name, pkg_arch)
+
+ Chef::Log.debug("Found Yum package: #{pkg_name} installed version: #{iv || "(none)"} candidate version: #{cv || "(none)"}")
+ end
+
+ @installed_version = installed_versions.length > 1 ? installed_versions : installed_versions[0]
+ @candidate_version = candidate_versions.length > 1 ? candidate_versions : candidate_versions[0]
+ end
- Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
- case line
- when /^(\S+)\s(\S+)$/
- @current_resource.package_name($1)
- @new_resource.version($2)
+ # Query the provided source file for the package name and version
+ def query_source_file
+ Chef::Log.debug("#{new_resource} checking rpm status")
+ shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' #{new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
+ case line
+ when /^(\S+)\s(\S+)\s(\S+)$/
+ n, v, a = $1, $2, $3
+
+ unless new_resource.package_name == n
+ Chef::Log.debug("#{new_resource} updating package_name from #{new_resource.package_name} to #{n} (per #{new_resource.source})")
+ new_resource.package_name(n)
end
- end
- @candidate_version << @new_resource.version
- installed_version << @yum.installed_version(@current_resource.package_name, arch)
- else
- package_name_array.each_with_index do |pkg, idx|
- # Don't overwrite an existing arch
- if arch
- name, parch = pkg, arch
- else
- name, parch = parse_arch(pkg)
- # if we parsed an arch from the name, update the name
- # to be just the package name.
- if parch
- if @new_resource.package_name.is_a?(Array)
- @new_resource.package_name[idx] = name
- else
- @new_resource.package_name(name)
- # only set the arch if it's a single package
- set_arch(parch)
- end
- end
+ unless new_resource.version == v
+ Chef::Log.debug("#{new_resource} updating version from #{new_resource.version} to #{v} (per #{new_resource.source})")
+ new_resource.version(v)
end
- if @new_resource.version
- new_resource =
- "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}"
- else
- new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}"
+ unless new_resource.arch == a
+ Chef::Log.debug("#{new_resource} updating architecture from #{new_resource.arch} to #{a} (per #{new_resource.source})")
+ new_resource.arch(a)
end
- Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
- installed_version << @yum.installed_version(name, parch)
- @candidate_version << @yum.candidate_version(name, parch)
- @arch << parch
end
end
- if installed_version.size == 1
- @current_resource.version(installed_version[0])
- @candidate_version = @candidate_version[0]
- @arch = @arch[0]
- else
- @current_resource.version(installed_version)
- end
+ @installed_version = @yum.installed_version(new_resource.package_name, new_resource.arch)
+ @candidate_version = new_resource.version
+ end
+
+ def yum_command(command)
+ command = "#{yum_binary} #{command}"
+ Chef::Log.debug("#{new_resource}: yum command: \"#{command}\"")
+ status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
- Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
- "#{@candidate_version || "(none)"}")
+ # This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
+ # considered fatal - meaning the rpm is still successfully installed. These issue
+ # cause yum to emit a non fatal warning but still exit(1). As there's currently no
+ # way to suppress this behavior and an exit(1) will break a Chef run we make an
+ # effort to trap these and re-run the same install command - it will either fail a
+ # second time or succeed.
+ #
+ # A cleaner solution would have to be done in python and better hook into
+ # yum/rpm to handle exceptions as we see fit.
+ if status.exitstatus == 1
+ status.stdout.each_line do |l|
+ # rpm-4.4.2.3 lib/psm.c line 2182
+ if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
+ Chef::Log.warn("#{new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
+ "so running install again to verify.")
+ status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
+ break
+ end
+ end
+ end
- @current_resource
+ if status.exitstatus > 0
+ command_output = "STDOUT: #{status.stdout}\nSTDERR: #{status.stderr}"
+ raise Chef::Exceptions::Exec, "#{command} returned #{status.exitstatus}:\n#{command_output}"
+ end
end
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_for_name(n))
+ # 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), safe_arch_array).any? do |n, v, a|
+ @yum.version_available?(n, v, a)
end
+
method = log_method = nil
methods = []
if all_avail
@@ -267,12 +308,14 @@ class Chef
# 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|
+ as_array(name).zip(current_version_array, as_array(version), safe_arch_array).each do |n, cv, v, a|
+ next if n.nil?
+
method = "install"
log_method = "installing"
- idx = package_name_array.index(n)
+
unless @yum.allow_multi_install.include?(n)
- if RPMVersion.parse(current_version_array[idx]) > RPMVersion.parse(v)
+ if RPMVersion.parse(cv) > RPMVersion.parse(v)
# We allow downgrading only in the evenit of single-package
# rules where the user explicitly allowed it
if allow_downgrade
@@ -280,13 +323,13 @@ class Chef
log_method = "downgrading"
else
# we bail like yum when the package is older
- raise Chef::Exceptions::Package, "Installed package #{n}-#{current_version_array[idx]} is newer " +
- "than candidate package #{n}-#{v}"
+ raise Chef::Exceptions::Package, "Installed package #{yum_syntax(n, cv, a)} is newer " +
+ "than candidate package #{yum_syntax(n, v, a)}"
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)
+ next if RPMVersion.parse(cv) == RPMVersion.parse(v)
methods << method
end
@@ -298,102 +341,26 @@ class Chef
repos = []
pkg_string_bits = []
- as_array(name).zip(as_array(version)).each do |n, v|
- idx = package_name_array.index(n)
- a = arch_for_name(n)
- s = ""
- unless v == current_version_array[idx]
- s = "#{n}-#{v}#{yum_arch(a)}"
+ as_array(name).zip(current_version_array, as_array(version), safe_arch_array).each do |n, cv, v, a|
+ next if n.nil?
+ unless v == cv
+ s = yum_syntax(n, v, a)
repo = @yum.package_repository(n, v, a)
repos << "#{s} from #{repo} repository"
pkg_string_bits << s
end
end
pkg_string = pkg_string_bits.join(" ")
- Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
+ Chef::Log.info("#{new_resource} #{log_method} #{repos.join(' ')}")
+ yum_command("-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("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
- else
- install_remote_package(name, version)
- end
-
- if flush_cache[:after]
- @yum.reload
- else
- @yum.reload_installed
- end
- end
-
- # Keep upgrades from trying to install an older candidate version. Can happen when a new
- # version is installed then removed from a repository, now the older available version
- # shows up as a viable install candidate.
- #
- # Can be done in upgrade_package but an upgraded from->to log message slips out
- #
- # 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_array.any?
- super
- 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")
- end
- end
-
- def upgrade_package(name, version)
- install_package(name, version)
- end
-
- def remove_package(name, version)
- if version
- remove_str = as_array(name).zip(as_array(version)).map do |n, v|
- a = arch_for_name(n)
- "#{[n, v].join('-')}#{yum_arch(a)}"
- end.join(" ")
- else
- remove_str = as_array(name).map do |n|
- a = arch_for_name(n)
- "#{n}#{yum_arch(a)}"
- end.join(" ")
- end
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
-
- if flush_cache[:after]
- @yum.reload
- else
- @yum.reload_installed
- end
- end
-
- def purge_package(name, version)
- remove_package(name, version)
- end
-
- def lock_package(name, version)
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} versionlock add #{name}")
- end
-
- def unlock_package(name, version)
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} versionlock delete #{name}")
- end
-
- private
-
+ # Allow for foo.x86_64 style package_name like yum uses in it's output
def parse_arch(package_name)
- # Allow for foo.x86_64 style package_name like yum uses in it's output
- #
if package_name =~ %r{^(.*)\.(.*)$}
new_package_name = $1
new_arch = $2
@@ -413,6 +380,31 @@ class Chef
return package_name, nil
end
+ #
+ # Dependency String Handling
+ #
+
+ # Iterate through the list of package_names given to us by the user and
+ # see if any of them are in the depenency format ("foo >= 1.1"). Modify
+ # the list of packages and versions to incorporate those values.
+ def convert_dependency_strings_into_packages
+ package_name_array.each_with_index do |n, index|
+ unless @yum.package_available?(n)
+ # If they aren't in the installed packages they could be a dependency.
+ dep = parse_dependency(n, new_version_array[index])
+ if dep
+ if new_resource.package_name.is_a?(Array)
+ new_resource.package_name(package_name_array - [n] + [dep.first])
+ new_resource.version(new_version_array - [new_version_array[index]] + [dep.last]) if dep.last
+ else
+ new_resource.package_name(dep.first)
+ new_resource.version(dep.last) if dep.last
+ end
+ end
+ end
+ end
+ end
+
# If we don't have the package we could have been passed a 'whatprovides' feature
#
# eg: yum install "perl(Config)"
@@ -423,6 +415,8 @@ class Chef
# matching them up with an actual package so the standard resource handling can apply.
#
# There is currently no support for filename matching.
+ #
+ # Note: This was largely left alone during the multipackage refactor
def parse_dependency(name, version)
# Transform the package_name into a requirement
@@ -442,9 +436,9 @@ class Chef
if packages.empty?
# Don't bother if we are just ensuring a package is removed - we don't need Provides data
- actions = Array(@new_resource.action)
+ actions = Array(new_resource.action)
unless actions.size == 1 && (actions[0] == :remove || actions[0] == :purge)
- Chef::Log.debug("#{@new_resource} couldn't match #{@new_resource.package_name} in " +
+ Chef::Log.debug("#{new_resource} couldn't match #{new_resource.package_name} in " +
"installed Provides, loading available Provides - this may take a moment")
@yum.reload_provides
packages = @yum.packages_from_require(yum_require)
@@ -467,7 +461,7 @@ class Chef
unique_names.uniq!
if unique_names.size > 1
- Chef::Log.warn("#{@new_resource} matched multiple Provides for #{@new_resource.package_name} " +
+ Chef::Log.warn("#{new_resource} matched multiple Provides for #{new_resource.package_name} " +
"but we can only use the first match: #{new_package_name}. Please use a more " +
"specific version.")
end
@@ -480,6 +474,63 @@ class Chef
end
end
+ #
+ # Misc Helpers
+ #
+
+ # Given an list of names and versions, generate the full yum syntax package name
+ def full_package_name(name, version)
+ as_array(name).zip(as_array(version), safe_arch_array).map do |n, v, a|
+ yum_syntax(n, v, a)
+ end
+ end
+
+ # Generate the yum syntax for the package
+ def yum_syntax(name, version, arch)
+ s = name
+ s += "-#{version}" if version
+ s += ".#{arch}" if arch
+ s
+ end
+
+ # Set the package name correctly based on whether it is a String or Array
+ def set_package_name(idx, name)
+ if new_resource.package_name.is_a?(String)
+ new_resource.package_name(name)
+ else
+ new_resource.package_name[idx] = name
+ end
+ end
+
+ # Set the architecture correcly based on whether it is a String or Array
+ def set_package_arch(idx, arch)
+ if new_resource.package_name.is_a?(String)
+ new_resource.arch(arch) unless arch.nil?
+ else
+ new_resource.arch ||= []
+ new_resource.arch[idx] = arch
+ end
+ end
+
+ # A cousin of package_name_array, return a list of the architectures
+ # defined in the resource.
+ def safe_arch_array
+ if new_resource.arch.is_a?(Array)
+ new_resource.arch
+ elsif new_resource.arch.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.arch ]
+ end
+ end
+
+ def flush_cache
+ if new_resource.respond_to?("flush_cache")
+ new_resource.flush_cache
+ else
+ { :before => false, :after => false }
+ end
+ end
end
end
end
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
index 2bb0d3dffe..babbf9b933 100644
--- a/spec/unit/provider/package/yum_spec.rb
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -82,10 +82,11 @@ describe Chef::Provider::Package::Yum do
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
allow(File).to receive(:exists?).with(@new_resource.source).and_return(true)
allow(@yum_cache).to receive(:installed_version).and_return(nil)
- shellout_double = double(:stdout => "chef-server-core 12.0.5-1")
+ shellout_double = double(:stdout => "chef-server-core 12.0.5-1 i386")
allow(@provider).to receive(:shell_out!).and_return(shellout_double)
@provider.load_current_resource
expect(@provider.candidate_version).to eql("12.0.5-1")
+ expect(@provider.new_resource.arch).to eql("i386")
end
end
@@ -94,14 +95,14 @@ describe Chef::Provider::Package::Yum do
expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(true)
expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
- expect(@provider.yum_binary).to eql("yum-deprecated")
+ expect(@provider.send(:yum_binary)).to eql("yum-deprecated")
end
it "when yum-deprecated does not exist" do
expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(false)
expect(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
- expect(@provider.yum_binary).to eql("yum")
+ expect(@provider.send(:yum_binary)).to eql("yum")
end
it "when the yum_binary is set on the resource" do
@@ -109,23 +110,23 @@ describe Chef::Provider::Package::Yum do
expect(File).not_to receive(:exist?)
expect(@yum_cache).to receive(:yum_binary=).with("/usr/bin/yum-something")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
- expect(@provider.yum_binary).to eql("/usr/bin/yum-something")
+ expect(@provider.send(:yum_binary)).to eql("/usr/bin/yum-something")
end
it "when the new_resource is a vanilla package class and yum-deprecated exists" do
- @new_resource = Chef::Resource::Package.new("cups")
+ @new_resource = Chef::Resource::YumPackage.new("cups")
expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(true)
expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
- expect(@provider.yum_binary).to eql("yum-deprecated")
+ expect(@provider.send(:yum_binary)).to eql("yum-deprecated")
end
it "when the new_resource is a vanilla package class and yum-deprecated does not exist" do
- @new_resource = Chef::Resource::Package.new("cups")
+ @new_resource = Chef::Resource::YumPackage.new("cups")
expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(false)
expect(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
- expect(@provider.yum_binary).to eql("yum")
+ expect(@provider.send(:yum_binary)).to eql("yum")
end
end
@@ -155,19 +156,17 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing")
expect(@provider.new_resource.arch).to eq("noarch")
- expect(@provider.arch).to eq("noarch")
@new_resource = Chef::Resource::YumPackage.new("testing.more.noarch")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing.more")
expect(@provider.new_resource.arch).to eq("noarch")
- expect(@provider.arch).to eq("noarch")
end
describe "when version constraint in package_name" do
it "should set package_version if no existing package_name is found and new_package_name is available" do
- @new_resource = Chef::Resource::Package.new("cups = 1.2.4-11.18.el5_2.3")
+ @new_resource = Chef::Resource::YumPackage.new("cups = 1.2.4-11.18.el5_2.3")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
allow(@yum_cache).to receive(:package_available?) { |pkg| pkg == "cups" ? true : false }
allow(@yum_cache).to receive(:packages_from_require) do |pkg|
@@ -211,14 +210,12 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing.beta3")
expect(@provider.new_resource.arch).to eq(nil)
- expect(@provider.arch).to eq(nil)
@new_resource = Chef::Resource::YumPackage.new("testing.beta3.more")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing.beta3.more")
expect(@provider.new_resource.arch).to eq(nil)
- expect(@provider.arch).to eq(nil)
end
it "should not set the arch when no existing package_name or new_package_name+new_arch is found" do
@@ -242,14 +239,12 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing.beta3")
expect(@provider.new_resource.arch).to eq(nil)
- expect(@provider.arch).to eq(nil)
@new_resource = Chef::Resource::YumPackage.new("testing.beta3.more")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing.beta3.more")
expect(@provider.new_resource.arch).to eq(nil)
- expect(@provider.arch).to eq(nil)
end
it "should ensure it doesn't clobber an existing arch if passed" do
@@ -539,7 +534,7 @@ describe Chef::Provider::Package::Yum do
end
it "should run yum localinstall if given a path to an rpm as the package" do
- @new_resource = Chef::Resource::Package.new("/tmp/emacs-21.4-20.el5.i386.rpm")
+ @new_resource = Chef::Resource::YumPackage.new("/tmp/emacs-21.4-20.el5.i386.rpm")
allow(::File).to receive(:exists?).and_return(true)
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
expect(@new_resource.source).to eq("/tmp/emacs-21.4-20.el5.i386.rpm")
@@ -550,22 +545,13 @@ describe Chef::Provider::Package::Yum do
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(
- "-d0 -e0 -y install cups-1.2.4-11.19.el5.i386"
- )
- @provider.install_package("cups", "1.2.4-11.19.el5")
- end
-
- it "should run yum install with package name+arch" do
@provider.load_current_resource
- allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
"-d0 -e0 -y install cups-1.2.4-11.19.el5.i386"
)
- @provider.install_package("cups.i386", "1.2.4-11.19.el5")
+ @provider.install_package("cups", "1.2.4-11.19.el5")
end
it "installs the package with the options given in the resource" do
@@ -699,7 +685,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")
+ @current_resource = Chef::Resource::YumPackage.new("cups")
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(
@@ -730,18 +716,15 @@ 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)
- @current_resource = Chef::Resource::Package.new("cups")
+ @current_resource = Chef::Resource::YumPackage.new("cups")
allow(@provider).to receive(:candidate_version).and_return("11")
- expect(@provider).to receive(:upgrade_package).with(
- "cups",
- "11"
- )
+ expect(@provider).to receive(:upgrade_package).with(["cups"], ["11"])
@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")
+ @current_resource = Chef::Resource::YumPackage.new("cups")
allow(@provider).to receive(:candidate_version).and_return(nil)
expect(@provider).not_to receive(:upgrade_package)
@provider.run_action(:upgrade)
@@ -749,19 +732,16 @@ describe Chef::Provider::Package::Yum do
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")
+ @current_resource = Chef::Resource::YumPackage.new("cups")
allow(@provider).to receive(:candidate_version).and_return("11")
- expect(@provider).to receive(:upgrade_package).with(
- "cups",
- "11"
- )
+ expect(@provider).to receive(:upgrade_package).with(["cups"], ["11"])
@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")
+ @current_resource = Chef::Resource::YumPackage.new("cups")
allow(@provider).to receive(:candidate_version).and_return("11")
expect(@provider).not_to receive(:upgrade_package)
@provider.run_action(:upgrade)
@@ -783,15 +763,6 @@ describe Chef::Provider::Package::Yum do
)
@provider.remove_package("emacs", "1.0")
end
-
- describe "when package name has arch in it" do
- it "should run yum remove with the package name and arc" do
- expect(@provider).to receive(:yum_command).with(
- "-d0 -e0 -y remove emacs-1.0.x86_64"
- )
- @provider.remove_package("emacs.x86_64", "1.0")
- end
- end
end
describe "when purging a package" do
@@ -829,7 +800,7 @@ describe Chef::Provider::Package::Yum do
"yum -d0 -e0 -y install emacs-1.0",
{ :timeout => Chef::Config[:yum_timeout] }
)
- @provider.yum_command("-d0 -e0 -y install emacs-1.0")
+ @provider.send(:yum_command, "-d0 -e0 -y install emacs-1.0")
end
it "should run yum once if it exits with a return code > 0 and no scriptlet failures" do
@@ -839,7 +810,7 @@ describe Chef::Provider::Package::Yum do
"yum -d0 -e0 -y install emacs-1.0",
{ :timeout => Chef::Config[:yum_timeout] }
)
- expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+ expect { @provider.send(:yum_command, "-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
end
it "should run yum once if it exits with a return code of 1 and %pre scriptlet failures" do
@@ -851,7 +822,7 @@ describe Chef::Provider::Package::Yum do
{ :timeout => Chef::Config[:yum_timeout] }
)
# will still raise an exception, can't stub out the subsequent call
- expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+ expect { @provider.send(:yum_command, "-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
end
it "should run yum twice if it exits with a return code of 1 and %post scriptlet failures" do
@@ -863,7 +834,7 @@ describe Chef::Provider::Package::Yum do
{ :timeout => Chef::Config[:yum_timeout] }
)
# will still raise an exception, can't stub out the subsequent call
- expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+ expect { @provider.send(:yum_command, "-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
end
it "should pass the yum_binary to the command if its specified" do
@@ -876,7 +847,7 @@ describe Chef::Provider::Package::Yum do
"yum-deprecated -d0 -e0 -y install emacs-1.0",
{ :timeout => Chef::Config[:yum_timeout] }
)
- @provider.yum_command("-d0 -e0 -y install emacs-1.0")
+ @provider.send(:yum_command, "-d0 -e0 -y install emacs-1.0")
end
end
end
@@ -2150,7 +2121,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
@node = Chef::Node.new
@events = Chef::EventDispatch::Dispatcher.new
@run_context = Chef::RunContext.new(@node, {}, @events)
- @new_resource = Chef::Resource::Package.new(%w{cups vim})
+ @new_resource = Chef::Resource::YumPackage.new(%w{cups vim})
@status = double("Status", :exitstatus => 0)
@yum_cache = double(
"Chef::Provider::Yum::YumCache",
@@ -2171,6 +2142,14 @@ describe "Chef::Provider::Package::Yum - Multi" do
@pid = double("PID")
end
+ describe "when evaluating the correctness of the resource" do
+ it "raises an error if the array lengths of package name, arch, and version do not match up" do
+ allow(@new_resource).to receive(:version).and_return(["1.1"])
+ allow(@new_resource).to receive(:arch).and_return(%w{x86_64 i386 i686})
+ expect { @provider.check_resource_semantics! }.to raise_error(Chef::Exceptions::InvalidResourceSpecification)
+ end
+ 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
@@ -2212,7 +2191,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
describe "when version constraint in package_name" do
it "should set package_version if no existing package_name is found and new_package_name is available" do
- @new_resource = Chef::Resource::Package.new(["cups = 1.2.4-11.18.el5_2.3", "emacs = 24.4"])
+ @new_resource = Chef::Resource::YumPackage.new(["cups = 1.2.4-11.18.el5_2.3", "emacs = 24.4"])
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
allow(@yum_cache).to receive(:package_available?) { |pkg| %w{cups emacs}.include?(pkg) ? true : false }
allow(@yum_cache).to receive(:candidate_version) do |pkg|
@@ -2230,7 +2209,8 @@ describe "Chef::Provider::Package::Yum - Multi" do
end
end
expect(Chef::Log).to receive(:debug).exactly(2).times.with(%r{matched 1 package,})
- expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{candidate version: \["1.2.4-11.18.el5_2.3", "24.4"\]})
+ expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{candidate version: 1.2.4-11.18.el5_2.3})
+ expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{candidate version: 24.4})
expect(Chef::Log).to receive(:debug).at_least(2).times.with(%r{checking yum info})
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq(%w{cups emacs})
@@ -2253,9 +2233,18 @@ describe "Chef::Provider::Package::Yum - Multi" do
@provider.install_package(%w{cups vim}, ["1.2.4-11.19.el5", "1.0"])
end
+ it "should not run yum install with nil package name" do
+ @provider.load_current_resource
+ allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
+ expect(@provider).to receive(:yum_command).with(
+ "-d0 -e0 -y install cups-1.2.4-11.19.el5"
+ )
+ @provider.install_package(["cups", nil], ["1.2.4-11.19.el5", nil])
+ 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(@new_resource).to receive(:arch).and_return(%w{i386 i386})
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
"-d0 -e0 -y install cups-1.2.4-11.19.el5.i386 vim-1.0.i386"
@@ -2276,7 +2265,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
end
it "should run yum install with the package name and version when name has arch" do
- @new_resource = Chef::Resource::Package.new(["cups.x86_64", "vim"])
+ @new_resource = Chef::Resource::YumPackage.new(["cups.x86_64", "vim"])
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)