summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Dibowitz <phil@ipom.com>2014-12-19 19:03:44 -0800
committerPhil Dibowitz <phil@ipom.com>2015-02-03 19:32:35 -0800
commit36ce3c58d7deb3467858ff7aefb05c819be9c416 (patch)
tree7832a6cb8c4581cd699fa09487bd75a384e67607
parent23cb1c709d83f1476e6a155a2ec8d0cdde14c0f9 (diff)
downloadchef-36ce3c58d7deb3467858ff7aefb05c819be9c416.tar.gz
Multipackge support
Allow the `package` provider to take an array of packages to handle in one transaction. This solves two large problems: * There are times when you cannot install two packages in sequence, like when a binary is moving between two packages - they *must* be done in the same transaction. * When using Chef to install the vast majority of your base system, it can make imaging take a very, very long time because executing yum or apt once for every single package is painfully slow. This solves both. The scaffolding is all there in the Package HWRP, plus the underlying implementation for both apt and yum, the two I have access to test.
-rw-r--r--lib/chef/mixin/get_source_from_package.rb1
-rw-r--r--lib/chef/provider/package.rb23
-rw-r--r--lib/chef/provider/package/apt.rb137
-rw-r--r--lib/chef/provider/package/yum.rb123
-rw-r--r--lib/chef/resource/package.rb4
5 files changed, 195 insertions, 93 deletions
diff --git a/lib/chef/mixin/get_source_from_package.rb b/lib/chef/mixin/get_source_from_package.rb
index 6d5cb56a27..fbf856079a 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.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..c2db4ba761 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -84,17 +84,19 @@ class Chef
end
def action_upgrade
- if candidate_version.nil?
+ if (@new_resource.package_name.is_a?(Array) && !candidate_version.any?) ||
+ (@new_resource.package_name.is_a?(String) && candidate_version.nil?)
Chef::Log.debug("#{@new_resource} no candidate version - nothing to do")
+ return
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
+ return
+ end
+ @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
end
@@ -113,8 +115,13 @@ class Chef
def removing_package?
if @current_resource.version.nil?
false # nothing to remove
+ elsif @current_resource.version.is_a?(Array) && !@current_resource.version.any?
+ # ! any? means it's all nil's, which means nothing is installed
+ false
elsif @new_resource.version.nil?
true # remove any version of a package
+ elsif @new_resource.version.is_a?(Array) && !@current_resource.version.any?
+ true # remove any version of all packages
elsif @new_resource.version == @current_resource.version
true # remove the version we have
else
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index fd132c817c..099a4a9b61 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -51,54 +51,89 @@ 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
+ if package.is_a?(Array)
+ final_installed_version = []
+ final_candidate_version = []
+ final_installed = []
+ final_virtual = []
+ end
+ installed = virtual = false
+ installed_version = candidate_version = nil
+
+ [package].flatten.each do |pkg|
+ installed = virtual = false
+ installed_version = candidate_version = nil
+ shell_out!("apt-cache#{expand_options(default_release_options)} policy #{pkg}").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}").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 +142,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 +165,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/yum.rb b/lib/chef/provider/package/yum.rb
index 505f5fd6a3..9b1481d286 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1090,10 +1090,20 @@ 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)
+ if @new_resource.package_name.is_a?(String)
+ 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)
+ else
+ installed_version = []
+ @candidate_version = []
+ @new_resource.package_name.each do |pkg|
+ installed_version << @yum.installed_version(pkg, arch)
+ @candidate_version << @yum.candidate_version(pkg, arch)
+ end
+ @current_resource.version(installed_version)
+ end
- @candidate_version = @yum.candidate_version(@new_resource.package_name, arch)
Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
"#{@candidate_version || "(none)"}")
@@ -1101,43 +1111,59 @@ 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)
- method = "install"
- log_method = "installing"
+ def install_remote_package(name, version)
+ # Work around yum not exiting with an error if a package doesn't exist
+ # for CHEF-2062
+ if !name.is_a?(Array) && @yum.version_available?(name, version, arch)
+ 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...
- 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}"
- end
+ # 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...
+ 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}"
end
end
+ end
- repo = @yum.package_repository(name, version, arch)
- Chef::Log.info("#{@new_resource} #{log_method} #{name}-#{version}#{yum_arch} from #{repo} repository")
+ repo = @yum.package_repository(name, version, arch)
+ Chef::Log.info("#{@new_resource} #{log_method} #{name}-#{version}#{yum_arch} from #{repo} repository")
- 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)"
- end
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{name}-#{version}#{yum_arch}")
+ elsif name.is_a?(Array)
+ index = 0
+ pkg_string = name.zip(version).map do |x|
+ s = ''
+ unless x[1] == @current_resource.version[index]
+ s = "#{x.join('-')}#{yum_arch}"
+ end
+ index += 1
+ s
+ end.join(' ')
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} install #{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]
@@ -1158,9 +1184,17 @@ class Chef
# Could be uninstalled or have no candidate
if @current_resource.version.nil? || candidate_version.nil?
super
- # Ensure the candidate is newer
- elsif RPMVersion.parse(candidate_version) > RPMVersion.parse(@current_resource.version)
+ elsif candidate_version.is_a?(String) &&
+ RPMVersion.parse(candidate_version) > RPMVersion.parse(@current_resource.version)
super
+ elsif candidate_version.is_a?(Array)
+ if candidate_version.zip(@current_resource.version).any? do |c, i|
+ RPMVersion.parse(c) > RPMVersion.parse(i)
+ end
+ super
+ else
+ Chef::Log.debug("#{@new_resource} are all at the latest versions - nothing to do")
+ end
else
Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
end
@@ -1172,10 +1206,21 @@ 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}")
+ if name.is_a?(Array)
+ remove_str = name.zip(version).map do |x|
+ "#{x.join('-')}#{yum_arch}"
+ end.join(' ')
+ else
+ remove_str = "#{name}-#{version}#{yum_arch}"
+ end
else
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}#{yum_arch}")
+ if name.is_a?(Array)
+ remove_str = name.map { |n| "#{n}#{yum_arch}" }.join(' ')
+ else
+ remove_str = "#{name}#{yum_arch}"
+ end
end
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
if flush_cache[:after]
@yum.reload
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