class Chef::Provider::Package

Attributes

candidate_version[RW]

Hook that subclasses use to populate the candidate_version(s)

@return [Array, String] candidate_version(s) may be a string or array

Public Class Methods

new(new_resource, run_context) click to toggle source
Calls superclass method Chef::Provider::new
# File lib/chef/provider/package.rb, line 47
def initialize(new_resource, run_context)
  super
  @candidate_version = nil
end

Public Instance Methods

action_lock() click to toggle source
# File lib/chef/provider/package.rb, line 190
def action_lock
  packages_locked = if respond_to?(:packages_all_locked?, true)
                      packages_all_locked?(Array(new_resource.package_name), Array(new_resource.version))
                    else
                      package_locked(new_resource.package_name, new_resource.version)
                    end
  unless packages_locked
    description = new_resource.version ? "version #{new_resource.version} of " : ""
    converge_by("lock #{description}package #{current_resource.package_name}") do
      multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
        lock_package(name, version)
        logger.info("#{new_resource} locked")
      end
    end
  else
    logger.trace("#{new_resource} is already locked")
  end
end
action_unlock() click to toggle source
# File lib/chef/provider/package.rb, line 209
def action_unlock
  packages_unlocked = if respond_to?(:packages_all_unlocked?, true)
                        packages_all_unlocked?(Array(new_resource.package_name), Array(new_resource.version))
                      else
                        !package_locked(new_resource.package_name, new_resource.version)
                      end
  unless packages_unlocked
    description = new_resource.version ? "version #{new_resource.version} of " : ""
    converge_by("unlock #{description}package #{current_resource.package_name}") do
      multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
        unlock_package(name, version)
        logger.info("#{new_resource} unlocked")
      end
    end
  else
    logger.trace("#{new_resource} is already unlocked")
  end
end
as_array(thing) click to toggle source

helper method used by subclasses

# File lib/chef/provider/package.rb, line 361
def as_array(thing)
  [ thing ].flatten
end
check_resource_semantics!() click to toggle source
# File lib/chef/provider/package.rb, line 56
def check_resource_semantics!
  # FIXME: this is not universally true and subclasses are needing to override this and no-ops it.  It should be turned into
  # another "subclass_directive" and the apt and yum providers should declare that they need this behavior.
  if new_resource.package_name.is_a?(Array) && !new_resource.source.nil?
    raise Chef::Exceptions::InvalidResourceSpecification, "You may not specify both multipackage and source"
  end
end
define_resource_requirements() click to toggle source
# File lib/chef/provider/package.rb, line 66
def define_resource_requirements
  # XXX: upgrade with a specific version doesn't make a whole lot of sense, but why don't we throw this anyway if it happens?
  # if not, shouldn't we raise to tell the user to use install instead of upgrade if they want to pin a version?
  requirements.assert(:install) do |a|
    a.assertion { candidates_exist_for_all_forced_changes? }
    a.failure_message(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{forced_packages_missing_candidates.join(', ')}")
    a.whyrun("Assuming a repository that offers #{forced_packages_missing_candidates.join(', ')} would have been configured")
  end

  # XXX: Does it make sense to pass in a source with :upgrade? Probably
  # not, but as with the above comment, we don't yet enforce such a thing,
  # so we'll just leave things as-is for now.
  requirements.assert(:upgrade, :install) do |a|
    a.assertion { candidates_exist_for_all_uninstalled? || new_resource.source }
    a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(', ')}")
    a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(', ')} would have been configured")
  end
end
expand_options(options) click to toggle source

used by subclasses. deprecated. use a_to_s instead.

# File lib/chef/provider/package.rb, line 280
def expand_options(options)
  # its deprecated but still work to do to deprecate it fully
  # Chef.deprecated(:package_misc, "expand_options is deprecated, use shell_out instead")
  if options
    " #{options.is_a?(Array) ? Shellwords.join(options) : options}"
  else
    ""
  end
end
have_any_matching_version?() click to toggle source
# File lib/chef/provider/package.rb, line 157
def have_any_matching_version?
  f = []
  new_version_array.each_with_index do |item, index|
    f << (item == current_version_array[index])
  end
  f.any?
end
install_package(name, version) click to toggle source
# File lib/chef/provider/package.rb, line 247
def install_package(name, version)
  raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :install"
end
load_current_resource() click to toggle source
# File lib/chef/provider/package.rb, line 64
def load_current_resource; end
lock_package(name, version) click to toggle source
# File lib/chef/provider/package.rb, line 271
def lock_package(name, version)
  raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :lock" )
end
multipackage_api_adapter(name, version) { |[name].flatten, [version].flatten| ... } click to toggle source

@todo use composition rather than inheritance

# File lib/chef/provider/package.rb, line 239
def multipackage_api_adapter(name, version)
  if use_multipackage_api?
    yield [name].flatten, [version].flatten
  else
    yield name, version
  end
end
options() click to toggle source
# File lib/chef/provider/package.rb, line 52
def options
  new_resource.options
end
package_locked(name, version) click to toggle source

for multipackage just implement packages_all_locked? properly and omit implementing this API

# File lib/chef/provider/package.rb, line 229
def package_locked(name, version)
  raise Chef::Exceptions::UnsupportedAction, "#{self} has no way to detect if package is locked"
end
prepare_for_installation() click to toggle source

Subclasses will override this to a method and provide a preseed file path

# File lib/chef/provider/package.rb, line 234
def prepare_for_installation
end
preseed_package(file) click to toggle source
# File lib/chef/provider/package.rb, line 263
def preseed_package(file)
  raise Chef::Exceptions::UnsupportedAction, "#{self} does not support pre-seeding package install/upgrade instructions"
end
purge_package(name, version) click to toggle source
# File lib/chef/provider/package.rb, line 259
def purge_package(name, version)
  raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :purge"
end
reconfig_package(name) click to toggle source
# File lib/chef/provider/package.rb, line 267
def reconfig_package(name)
  raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :reconfig" )
end
remove_package(name, version) click to toggle source
# File lib/chef/provider/package.rb, line 255
def remove_package(name, version)
  raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :remove"
end
removing_package?() click to toggle source
# File lib/chef/provider/package.rb, line 165
def removing_package?
  if !current_version_array.any?
    # ! any? means it's all nil's, which means nothing is installed
    false
  elsif !new_version_array.any?
    true # remove any version of all packages
  elsif have_any_matching_version?
    true # remove the version we have
  else
    false # we don't have the version we want to remove
  end
end
target_version_already_installed?(current_version, target_version) click to toggle source

This method performs a strict equality check between two strings representing version numbers

This function will eventually be deprecated in favour of the below version_equals function.

# File lib/chef/provider/package.rb, line 314
def target_version_already_installed?(current_version, target_version)
  version_equals?(current_version, target_version)
end
unlock_package(name, version) click to toggle source
# File lib/chef/provider/package.rb, line 275
def unlock_package(name, version)
  raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :unlock" )
end
upgrade_package(name, version) click to toggle source
# File lib/chef/provider/package.rb, line 251
def upgrade_package(name, version)
  raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :upgrade"
end
version_compare(v1, v2) click to toggle source

This function compares two version numbers and returns 'spaceship operator' style results, ie: if v1 < v2 then return -1 if v1 = v2 then return 0 if v1 > v2 then return 1 if v1 and v2 are not comparable then return nil

By default, this function will use Gem::Version comparison. Subclasses can reimplement this method for package-management system specific versions.

(In other words, pull requests to introduce domain specific mangling of versions into this method will be closed – that logic must go into the subclass – we understand that this is far from perfect but it is a better default than outright buggy things like v1.to_f <=> v2.to_f)

# File lib/chef/provider/package.rb, line 341
def version_compare(v1, v2)
  gem_v1 = Gem::Version.new(v1.gsub(/\A\s*(#{Gem::Version::VERSION_PATTERN}).*/, '\1'))
  gem_v2 = Gem::Version.new(v2.gsub(/\A\s*(#{Gem::Version::VERSION_PATTERN}).*/, '\1'))

  gem_v1 <=> gem_v2
end
version_equals?(v1, v2) click to toggle source

This method performs a strict equality check between two strings representing version numbers

# File lib/chef/provider/package.rb, line 323
def version_equals?(v1, v2)
  return false unless v1 && v2
  v1 == v2
end
version_requirement_satisfied?(current_version, new_version) click to toggle source

Check the current_version against the new_resource.version, possibly using fuzzy matching criteria.

Subclasses MAY override this to provide fuzzy matching on the resource ('>=' and '~>' stuff)

`version_satisfied_by?(version, constraint)` might be a better name to make this generic.

# File lib/chef/provider/package.rb, line 355
def version_requirement_satisfied?(current_version, new_version)
  target_version_already_installed?(current_version, new_version)
end

Private Instance Methods

allow_downgrade() click to toggle source
# File lib/chef/provider/package.rb, line 591
def allow_downgrade
  if new_resource.respond_to?("allow_downgrade")
    new_resource.allow_downgrade
  else
    true
  end
end
candidate_version_array() click to toggle source

@return [Array] candidate_version(s) as an array

# File lib/chef/provider/package.rb, line 541
def candidate_version_array
  # NOTE: even with use_multipackage_api candidate_version may be a bare nil and need wrapping
  # ( looking at you, dpkg provider... )
  Chef::Decorator::LazyArray.new { [ candidate_version ].flatten }
end
candidates_exist_for_all_forced_changes?() click to toggle source

This looks for packages which have a new_version and a current_version, and they are different (a “forced change”) and for which there is no candidate. This is an edge condition that candidates_exist_for_all_uninstalled? does not catch since in this case it is not uninstalled but must be installed anyway and no version exists.

@return [Boolean] valid candidates exist for all uninstalled packages

# File lib/chef/provider/package.rb, line 497
def candidates_exist_for_all_forced_changes?
  forced_packages_missing_candidates.empty?
end
candidates_exist_for_all_uninstalled?() click to toggle source

Check the list of current_version_array and candidate_version_array. For any of the packages if both versions are missing (uninstalled and no candidate) this will be an unsolvable error.

@return [Boolean] valid candidates exist for all uninstalled packages

# File lib/chef/provider/package.rb, line 473
def candidates_exist_for_all_uninstalled?
  packages_missing_candidates.empty?
end
current_version_array() click to toggle source

@return [Array] current_version(s) as an array

# File lib/chef/provider/package.rb, line 548
def current_version_array
  @current_version_array ||= [ current_resource.version ].flatten
end
each_package() { |package_name, new_version, current_version, candidate_version| ... } click to toggle source

Helper to iterate over all the indexed *_array's in sync

@yield [package_name, new_version, current_version, candidate_version] Description of block

# File lib/chef/provider/package.rb, line 521
def each_package
  package_name_array.each_with_index do |package_name, i|
    candidate_version = candidate_version_array[i]
    current_version = current_version_array[i]
    new_version = new_version_array[i]
    yield package_name, new_version, current_version, candidate_version
  end
end
forced_packages_missing_candidates() click to toggle source

Returns an array of all forced packages which are missing candidate versions

@return [Array] names of packages missing candidates

# File lib/chef/provider/package.rb, line 504
def forced_packages_missing_candidates
  @forced_packages_missing_candidates ||=
    begin
      missing = []
      each_package do |package_name, new_version, current_version, candidate_version|
        next if new_version.nil? || current_version.nil?
        if !version_requirement_satisfied?(current_version, new_version) && candidate_version.nil?
          missing.push(package_name)
        end
      end
      missing
    end
end
install_description() click to toggle source
# File lib/chef/provider/package.rb, line 101
def install_description
  description = []
  target_version_array.each_with_index do |target_version, i|
    next if target_version.nil?
    package_name = package_name_array[i]
    description << "install version #{target_version} of package #{package_name}"
  end
  description
end
multipackage?() click to toggle source

@return [Boolean] if we're doing a multipackage install or not

# File lib/chef/provider/package.rb, line 531
def multipackage?
  @multipackage_bool ||= new_resource.package_name.is_a?(Array)
end
new_version_array() click to toggle source

@return [Array] new_version(s) as an array

# File lib/chef/provider/package.rb, line 553
def new_version_array
  @new_version_array ||= [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
end
package_name_array() click to toggle source

@return [Array] package_name(s) as an array

# File lib/chef/provider/package.rb, line 536
def package_name_array
  @package_name_array ||= [ new_resource.package_name ].flatten
end
package_names_for_targets() click to toggle source

Returns the package names which need to be modified. If the resource was called with an array of packages then this will return an array of packages to update (may have 0 or 1 entries). If the resource was called with a non-array package_name to manage then this will return a string rather than an Array. The output of this is meant to be fed into subclass interfaces to install/upgrade packages and not all of them are Array-aware.

@return [String, Array<String>] package_name(s) to actually update/install

# File lib/chef/provider/package.rb, line 374
def package_names_for_targets
  package_names_for_targets = []
  target_version_array.each_with_index do |target_version, i|
    if !target_version.nil?
      package_name = package_name_array[i]
      package_names_for_targets.push(package_name)
    else
      package_names_for_targets.push(nil) if allow_nils?
    end
  end
  multipackage? ? package_names_for_targets : package_names_for_targets[0]
end
packages_missing_candidates() click to toggle source

Returns array of all packages which are missing candidate versions.

@return [Array<String>] names of packages missing candidates

# File lib/chef/provider/package.rb, line 480
def packages_missing_candidates
  @packages_missing_candidates ||=
    begin
      missing = []
      each_package do |package_name, new_version, current_version, candidate_version|
        missing.push(package_name) if current_version.nil? && candidate_version.nil?
      end
      missing
    end
end
resolved_source_array() click to toggle source

Helper to handle use_package_name_for_source to convert names into local packages to install.

@return [Array] Array of sources with package_names converted to sources

# File lib/chef/provider/package.rb, line 575
def resolved_source_array
  @resolved_source_array ||=
    begin
      source_array.each_with_index.map do |source, i|
        package_name = package_name_array[i]
        # we require at least one '/' in the package_name to avoid [XXX_]package 'foo' breaking due to a random 'foo' file in cwd
        if use_package_name_for_source? && source.nil? && package_name.match(/#{::File::SEPARATOR}/) && ::File.exist?(package_name)
          logger.trace("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.")
          package_name
        else
          source
        end
      end
    end
end
source_array() click to toggle source

TIP: less error prone to simply always call resolved_source_array, even if you don't think that you need to.

@return [Array] new_resource.source as an array

# File lib/chef/provider/package.rb, line 561
def source_array
  @source_array ||=
    begin
      if new_resource.source.nil?
        package_name_array.map { nil }
      else
        [ new_resource.source ].flatten
      end
    end
end
target_version_array() click to toggle source

Return an array indexed the same as *_version_array which contains either the target version to install/upgrade to or else nil if the package is not being modified.

@return [Array<String,NilClass>] array of package versions which need to be upgraded (nil = not being upgraded)

# File lib/chef/provider/package.rb, line 410
def target_version_array
  @target_version_array ||=
    begin
      target_version_array = []
      each_package do |package_name, new_version, current_version, candidate_version|
        case action
        when :upgrade
          if version_equals?(current_version, new_version)
            # this is an odd use case
            logger.trace("#{new_resource} #{package_name} #{new_version} is already installed -- you are equality pinning with an :upgrade action, this may be deprecated in the future")
            target_version_array.push(nil)
          elsif version_equals?(current_version, candidate_version)
            logger.trace("#{new_resource} #{package_name} #{candidate_version} is already installed")
            target_version_array.push(nil)
          elsif candidate_version.nil?
            logger.trace("#{new_resource} #{package_name} has no candidate_version to upgrade to")
            target_version_array.push(nil)
          elsif current_version.nil?
            logger.trace("#{new_resource} has no existing installed version. Installing install #{candidate_version}")
            target_version_array.push(candidate_version)
          elsif !allow_downgrade && version_compare(current_version, candidate_version) == 1
            logger.trace("#{new_resource} #{package_name} has installed version #{current_version}, which is newer than available version #{candidate_version}. Skipping...)")
            target_version_array.push(nil)
          else
            logger.trace("#{new_resource} #{package_name} is out of date, will upgrade to #{candidate_version}")
            target_version_array.push(candidate_version)
          end

        when :install
          if new_version
            if version_requirement_satisfied?(current_version, new_version)
              logger.trace("#{new_resource} #{package_name} #{current_version} satisifies #{new_version} requirement")
              target_version_array.push(nil)
            elsif current_version && !allow_downgrade && version_compare(current_version, new_version) == 1
              logger.warn("#{new_resource} #{package_name} has installed version #{current_version}, which is newer than available version #{new_version}. Skipping...)")
              target_version_array.push(nil)
            else
              logger.trace("#{new_resource} #{package_name} #{current_version} needs updating to #{new_version}")
              target_version_array.push(new_version)
            end
          elsif current_version.nil?
            logger.trace("#{new_resource} #{package_name} not installed, installing #{candidate_version}")
            target_version_array.push(candidate_version)
          else
            logger.trace("#{new_resource} #{package_name} #{current_version} already installed")
            target_version_array.push(nil)
          end

        else
          # in specs please test the public interface provider.run_action(:install) instead of provider.action_install
          raise "internal error - target_version_array in package provider does not understand this action"
        end
      end

      target_version_array
    end
end
upgrade_description() click to toggle source
# File lib/chef/provider/package.rb, line 128
def upgrade_description
  log_allow_downgrade = allow_downgrade ? "(allow_downgrade)" : ""
  description = []
  target_version_array.each_with_index do |target_version, i|
    next if target_version.nil?
    package_name = package_name_array[i]
    candidate_version = candidate_version_array[i]
    current_version = current_version_array[i] || "uninstalled"
    description << "upgrade#{log_allow_downgrade} package #{package_name} from #{current_version} to #{candidate_version}"
  end
  description
end
versions_for_targets() click to toggle source

Returns the package versions which need to be modified. If the resource was called with an array of packages then this will return an array of versions to update (may have 0 or 1 entries). If the resource was called with a non-array package_name to manage then this will return a string rather than an Array. The output of this is meant to be fed into subclass interfaces to install/upgrade packages and not all of them are Array-aware.

@return [String, Array<String>] package version(s) to actually update/install

# File lib/chef/provider/package.rb, line 394
def versions_for_targets
  versions_for_targets = []
  target_version_array.each_with_index do |target_version, i|
    if !target_version.nil?
      versions_for_targets.push(target_version)
    else
      versions_for_targets.push(nil) if allow_nils?
    end
  end
  multipackage? ? versions_for_targets : versions_for_targets[0]
end