diff options
Diffstat (limited to 'lib/chef/provider/package/apt.rb')
-rw-r--r-- | lib/chef/provider/package/apt.rb | 226 |
1 files changed, 121 insertions, 105 deletions
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb index e109c9966a..3f8c34f50c 100644 --- a/lib/chef/provider/package/apt.rb +++ b/lib/chef/provider/package/apt.rb @@ -1,6 +1,6 @@ # -# Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Author:: Adam Jacob (<adam@chef.io>) +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,133 +16,77 @@ # limitations under the License. # -require 'chef/provider/package' -require 'chef/mixin/command' -require 'chef/resource/package' +require "chef/provider/package" +require "chef/resource/apt_package" class Chef class Provider class Package class Apt < Chef::Provider::Package + use_multipackage_api provides :package, platform_family: "debian" provides :apt_package, os: "linux" - # return [Hash] mapping of package name to Boolean value - attr_accessor :is_virtual_package - def initialize(new_resource, run_context) super - @is_virtual_package = {} end def load_current_resource - @current_resource = Chef::Resource::Package.new(@new_resource.name) - @current_resource.package_name(@new_resource.package_name) - check_all_packages_state(@new_resource.package_name) - @current_resource + @current_resource = Chef::Resource::AptPackage.new(new_resource.name) + current_resource.package_name(new_resource.package_name) + current_resource.version(get_current_versions) + current_resource end def define_resource_requirements super requirements.assert(:all_actions) do |a| - a.assertion { !@new_resource.source } - a.failure_message(Chef::Exceptions::Package, 'apt package provider cannot handle source attribute. Use dpkg provider instead') + a.assertion { !new_resource.source } + a.failure_message(Chef::Exceptions::Package, "apt package provider cannot handle source attribute. Use dpkg provider instead") end end - def default_release_options - # Use apt::Default-Release option only if provider supports it - "-o APT::Default-Release=#{@new_resource.default_release}" if @new_resource.respond_to?(:default_release) && @new_resource.default_release + def package_data + @package_data ||= Hash.new do |hash, key| + hash[key] = package_data_for(key) + end end - def check_package_state(pkg) - is_virtual_package = false - installed = false - installed_version = nil - candidate_version = nil - - shell_out_with_timeout!("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 - is_virtual_package = true - showpkg = shell_out_with_timeout!("apt-cache showpkg #{pkg}").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}]") - ret = check_package_state(providers.keys.first) - installed = ret[:installed] - installed_version = ret[:installed_version] - else - Chef::Log.debug("#{@new_resource} candidate version is #{$1}") - end - end + def get_current_versions + package_name_array.map do |package_name| + package_data[package_name][:current_version] end - - return { - installed_version: installed_version, - installed: installed, - candidate_version: candidate_version, - is_virtual_package: is_virtual_package, - } end - def check_all_packages_state(package) - installed_version = {} - candidate_version = {} - installed = {} - - [package].flatten.each do |pkg| - ret = check_package_state(pkg) - is_virtual_package[pkg] = ret[:is_virtual_package] - installed[pkg] = ret[:installed] - installed_version[pkg] = ret[:installed_version] - candidate_version[pkg] = ret[:candidate_version] + def get_candidate_versions + package_name_array.map do |package_name| + package_data[package_name][:candidate_version] end + end + + def candidate_version + @candidate_version ||= get_candidate_versions + end - if package.is_a?(Array) - @candidate_version = [] - final_installed_version = [] - [package].flatten.each do |pkg| - @candidate_version << candidate_version[pkg] - final_installed_version << installed_version[pkg] + def package_locked(name, version) + islocked = false + locked = shell_out_compact_timeout!("apt-mark", "showhold") + locked.stdout.each_line do |line| + line_package = line.strip + if line_package == name + islocked = true end - @current_resource.version(final_installed_version) - else - @candidate_version = candidate_version[package] - @current_resource.version(installed_version[package]) end + islocked end def install_package(name, version) - name_array = [ name ].flatten - version_array = [ version ].flatten - package_name = name_array.zip(version_array).map do |n, v| - is_virtual_package[n] ? n : "#{n}=#{v}" - end.join(' ') - run_noninteractive("apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}") + package_name = name.zip(version).map do |n, v| + package_data[n][:virtual] ? n : "#{n}=#{v}" + end + run_noninteractive("apt-get", "-q", "-y", default_release_options, options, "install", package_name) end def upgrade_package(name, version) @@ -150,24 +94,35 @@ class Chef end def remove_package(name, version) - package_name = [ name ].flatten.join(' ') - run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}") + package_name = name.map do |n| + package_data[n][:virtual] ? resolve_virtual_package_name(n) : n + end + run_noninteractive("apt-get", "-q", "-y", options, "remove", package_name) end def purge_package(name, version) - package_name = [ name ].flatten.join(' ') - run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} purge #{package_name}") + package_name = name.map do |n| + package_data[n][:virtual] ? resolve_virtual_package_name(n) : n + end + run_noninteractive("apt-get", "-q", "-y", options, "purge", package_name) end def preseed_package(preseed_file) - Chef::Log.info("#{@new_resource} pre-seeding package installation instructions") - run_noninteractive("debconf-set-selections #{preseed_file}") + Chef::Log.info("#{new_resource} pre-seeding package installation instructions") + run_noninteractive("debconf-set-selections", preseed_file) end def reconfig_package(name, version) - package_name = [ name ].flatten.join(' ') - Chef::Log.info("#{@new_resource} reconfiguring") - run_noninteractive("dpkg-reconfigure #{package_name}") + Chef::Log.info("#{new_resource} reconfiguring") + run_noninteractive("dpkg-reconfigure", name) + end + + def lock_package(name, version) + run_noninteractive("apt-mark", options, "hold", name) + end + + def unlock_package(name, version) + run_noninteractive("apt-mark", options, "unhold", name) end private @@ -175,8 +130,69 @@ class Chef # Runs command via shell_out with magic environment to disable # interactive prompts. Command is run with default localization rather # than forcing locale to "C", so command output may not be stable. - def run_noninteractive(command) - shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil }) + def run_noninteractive(*args) + shell_out_compact_timeout!(*args, env: { "DEBIAN_FRONTEND" => "noninteractive" }) + end + + def default_release_options + # Use apt::Default-Release option only if provider supports it + if new_resource.respond_to?(:default_release) && new_resource.default_release + [ "-o", "APT::Default-Release=#{new_resource.default_release}" ] + end + end + + def resolve_package_versions(pkg) + current_version = nil + candidate_version = nil + run_noninteractive("apt-cache", default_release_options, "policy", pkg).stdout.each_line do |line| + case line + when /^\s{2}Installed: (.+)$/ + current_version = ( $1 != "(none)" ) ? $1 : nil + Chef::Log.debug("#{new_resource} installed version for #{pkg} is #{$1}") + when /^\s{2}Candidate: (.+)$/ + candidate_version = ( $1 != "(none)" ) ? $1 : nil + Chef::Log.debug("#{new_resource} candidate version for #{pkg} is #{$1}") + end + end + [ current_version, candidate_version ] + end + + def resolve_virtual_package_name(pkg) + showpkg = run_noninteractive("apt-cache", "showpkg", pkg).stdout + partitions = showpkg.rpartition(/Reverse Provides: ?#{$/}/) + return nil if partitions[0] == "" && partitions[1] == "" # not found in output + set = partitions[2].lines.each_with_object(Set.new) do |line, acc| + # there may be multiple reverse provides for a single package + acc.add(line.split[0]) + end + if set.size > 1 + raise Chef::Exceptions::Package, "#{new_resource.package_name} is a virtual package provided by multiple packages, you must explicitly select one" + end + set.to_a.first + end + + def package_data_for(pkg) + virtual = false + current_version = nil + candidate_version = nil + + current_version, candidate_version = resolve_package_versions(pkg) + + if candidate_version.nil? + newpkg = resolve_virtual_package_name(pkg) + + if newpkg + virtual = true + Chef::Log.info("#{new_resource} is a virtual package, actually acting on package[#{newpkg}]") + current_version, candidate_version = resolve_package_versions(newpkg) + end + end + + { + current_version: current_version, + candidate_version: candidate_version, + virtual: virtual, + } end end |