class Chef::Provider::Package::Yum

Public Class Methods

new(new_resource, run_context) click to toggle source
Calls superclass method Chef::Mixin::GetSourceFromPackage.new
# File lib/chef/provider/package/yum.rb, line 954
def initialize(new_resource, run_context)
  super

  @yum = YumCache.instance
end

Public Instance Methods

action_upgrade() click to toggle source

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?

Calls superclass method Chef::Provider::Package#action_upgrade
# File lib/chef/provider/package/yum.rb, line 1157
def action_upgrade
  # 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)
    super
  else
    Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
  end
end
allow_downgrade() click to toggle source
# File lib/chef/provider/package/yum.rb, line 979
def allow_downgrade
  if @new_resource.respond_to?("allow_downgrade")
    @new_resource.allow_downgrade
  else
    false
  end
end
arch() click to toggle source

Extra attributes

# File lib/chef/provider/package/yum.rb, line 963
def arch
  if @new_resource.respond_to?("arch")
    @new_resource.arch
  else
    nil
  end
end
flush_cache() click to toggle source
# File lib/chef/provider/package/yum.rb, line 971
def flush_cache
  if @new_resource.respond_to?("flush_cache")
    @new_resource.flush_cache
  else
    { :before => false, :after => false }
  end
end
install_package(name, version) click to toggle source
# File lib/chef/provider/package/yum.rb, line 1104
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"

      # 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")

      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
  end

  if flush_cache[:after]
    @yum.reload
  else
    @yum.reload_installed
  end
end
load_current_resource() click to toggle source

Standard Provider methods for Parent

# File lib/chef/provider/package/yum.rb, line 1028
def load_current_resource
  if flush_cache[:before]
    @yum.reload
  end

  if @new_resource.options
    repo_control = []
    @new_resource.options.split.each do |opt|
      if opt =~ %r{--(enable|disable)repo=.+}
        repo_control << opt
      end
    end

    if repo_control.size > 0
      @yum.enable_extra_repo_control(repo_control.join(" "))
    else
      @yum.disable_extra_repo_control
    end
  else
    @yum.disable_extra_repo_control
  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"

  # 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
  end

  # Don't overwrite an existing arch
  unless arch
    parse_arch
  end

  @current_resource = Chef::Resource::Package.new(@new_resource.name)
  @current_resource.package_name(@new_resource.package_name)

  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 rpm status")
    shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
      case line
      when /([\w\d_.-]+)\s([\w\d_.-]+)/
        @current_resource.package_name($1)
        @new_resource.version($2)
      end
    end
  end

  if @new_resource.version
    new_resource = "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch}"
  else
    new_resource = "#{@new_resource.package_name}#{yum_arch}"
  end

  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)

  Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
                  "#{@candidate_version || "(none)"}")

  @current_resource
end
purge_package(name, version) click to toggle source
# File lib/chef/provider/package/yum.rb, line 1187
def purge_package(name, version)
  remove_package(name, version)
end
remove_package(name, version) click to toggle source
# File lib/chef/provider/package/yum.rb, line 1173
def remove_package(name, version)
  if version
    yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}-#{version}#{yum_arch}")
  else
    yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}#{yum_arch}")
  end

  if flush_cache[:after]
    @yum.reload
  else
    @yum.reload_installed
  end
end
upgrade_package(name, version) click to toggle source
# File lib/chef/provider/package/yum.rb, line 1169
def upgrade_package(name, version)
  install_package(name, version)
end
yum_arch() click to toggle source

Helpers

# File lib/chef/provider/package/yum.rb, line 990
def yum_arch
  arch ? ".#{arch}" : nil
end
yum_command(command) click to toggle source
# File lib/chef/provider/package/yum.rb, line 994
def yum_command(command)
  status, stdout, stderr = output_of_command(command, {:timeout => Chef::Config[:yum_timeout]})

  # 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
    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, stdout, stderr = output_of_command(command, {:timeout => Chef::Config[:yum_timeout]})
        break
      end
    end
  end

  if status.exitstatus > 0
    command_output = "STDOUT: #{stdout}"
    command_output << "STDERR: #{stderr}"
    handle_command_failures(status, command_output, {})
  end
end

Private Instance Methods

parse_arch() click to toggle source
# File lib/chef/provider/package/yum.rb, line 1193
def parse_arch
  # Allow for foo.x86_64 style package_name like yum uses in it's output
  #
  if @new_resource.package_name =~ %r{^(.*)\.(.*)$}
    new_package_name = $1
    new_arch = $2
    # foo.i386 and foo.beta1 are both valid package names or expressions of an arch.
    # Ensure we don't have an existing package matching package_name, then ensure we at
    # least have a match for the new_package+new_arch before we overwrite. If neither
    # then fall through to standard package handling.
    if (@yum.installed_version(@new_resource.package_name).nil? and @yum.candidate_version(@new_resource.package_name).nil?) and
         (@yum.installed_version(new_package_name, new_arch) or @yum.candidate_version(new_package_name, new_arch))
       @new_resource.package_name(new_package_name)
       @new_resource.arch(new_arch)
    end
  end
end
parse_dependency() click to toggle source

If we don't have the package we could have been passed a 'whatprovides' feature

eg: yum install “perl(Config)”

yum install "mtr = 2:0.71-3.1"
yum install "mtr > 2:0.71"

We support resolving these out of the Provides data imported from yum-dump.py and matching them up with an actual package so the standard resource handling can apply.

There is currently no support for filename matching.

# File lib/chef/provider/package/yum.rb, line 1221
def parse_dependency
  # Transform the package_name into a requirement
  yum_require = RPMRequire.parse(@new_resource.package_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)

  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)
    unless actions.size == 1 and (actions[0] == :remove || actions[0] == :purge)
      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)
    end
  end

  unless packages.empty?
    new_package_name = packages.first.name
    Chef::Log.debug("#{@new_resource} no package found for #{@new_resource.package_name} " +
                    "but matched Provides for #{new_package_name}")

    # Ensure it's not the same package under a different architecture
    unique_names = []
    packages.each do |pkg|
      unique_names << "#{pkg.name}-#{pkg.version.evr}"
    end
    unique_names.uniq!

    if unique_names.size > 1
      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

    @new_resource.package_name(new_package_name)
  end
end