diff options
author | Phil Dibowitz <phil@ipom.com> | 2014-12-19 19:03:44 -0800 |
---|---|---|
committer | Phil Dibowitz <phil@ipom.com> | 2015-02-03 19:32:35 -0800 |
commit | 36ce3c58d7deb3467858ff7aefb05c819be9c416 (patch) | |
tree | 7832a6cb8c4581cd699fa09487bd75a384e67607 | |
parent | 23cb1c709d83f1476e6a155a2ec8d0cdde14c0f9 (diff) | |
download | chef-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.rb | 1 | ||||
-rw-r--r-- | lib/chef/provider/package.rb | 23 | ||||
-rw-r--r-- | lib/chef/provider/package/apt.rb | 137 | ||||
-rw-r--r-- | lib/chef/provider/package/yum.rb | 123 | ||||
-rw-r--r-- | lib/chef/resource/package.rb | 4 |
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 |