class Chef::Provider::Package
Attributes
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
# File lib/chef/provider/package.rb, line 47 def initialize(new_resource, run_context) super @candidate_version = nil end
Public Instance Methods
# File lib/chef/provider/package.rb, line 222 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
# File lib/chef/provider/package.rb, line 241 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
helper method used by subclasses
# File lib/chef/provider/package.rb, line 424 def as_array(thing) [ thing ].flatten end
# 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
# 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
used by subclasses. deprecated. use a_to_s instead.
# File lib/chef/provider/package.rb, line 308 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_compact or shell_out_compact_timeout instead") if options " #{options.is_a?(Array) ? Shellwords.join(options) : options}" else "" end end
@todo: extract apt/dpkg specific preseeding to a helper class
# File lib/chef/provider/package.rb, line 383 def get_preseed_file(name, version) resource = preseed_resource(name, version) resource.run_action(:create) logger.trace("#{new_resource} fetched preseed file to #{resource.path}") if resource.updated_by_last_action? resource.path else false end end
# File lib/chef/provider/package.rb, line 164 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
# File lib/chef/provider/package.rb, line 275 def install_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :install" end
# File lib/chef/provider/package.rb, line 64 def load_current_resource; end
# File lib/chef/provider/package.rb, line 299 def lock_package(name, version) raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :lock" ) end
@todo use composition rather than inheritance
# File lib/chef/provider/package.rb, line 267 def multipackage_api_adapter(name, version) if use_multipackage_api? yield [name].flatten, [version].flatten else yield name, version end end
# File lib/chef/provider/package.rb, line 52 def options new_resource.options end
for multipackage just implement packages_all_locked? properly and omit implementing this API
# File lib/chef/provider/package.rb, line 261 def package_locked(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} has no way to detect if package is locked" end
# File lib/chef/provider/package.rb, line 291 def preseed_package(file) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support pre-seeding package install/upgrade instructions" end
@todo: extract apt/dpkg specific preseeding to a helper class
# File lib/chef/provider/package.rb, line 396 def preseed_resource(name, version) # A directory in our cache to store this cookbook's preseed files in file_cache_dir = Chef::FileCache.create_cache_path("preseed/#{new_resource.cookbook_name}") # The full path where the preseed file will be stored cache_seed_to = "#{file_cache_dir}/#{name}-#{version}.seed" logger.trace("#{new_resource} fetching preseed file to #{cache_seed_to}") if template_available?(new_resource.response_file) logger.trace("#{new_resource} fetching preseed file via Template") remote_file = Chef::Resource::Template.new(cache_seed_to, run_context) remote_file.variables(new_resource.response_file_variables) elsif cookbook_file_available?(new_resource.response_file) logger.trace("#{new_resource} fetching preseed file via cookbook_file") remote_file = Chef::Resource::CookbookFile.new(cache_seed_to, run_context) else message = "No template or cookbook file found for response file #{new_resource.response_file}" raise Chef::Exceptions::FileNotFound, message end remote_file.cookbook_name = new_resource.cookbook_name remote_file.source(new_resource.response_file) remote_file.backup(false) remote_file end
# File lib/chef/provider/package.rb, line 287 def purge_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :purge" end
# File lib/chef/provider/package.rb, line 295 def reconfig_package(name, version) raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :reconfig" ) end
# File lib/chef/provider/package.rb, line 283 def remove_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :remove" end
# File lib/chef/provider/package.rb, line 172 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
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 342 def target_version_already_installed?(current_version, target_version) version_equals?(current_version, target_version) end
# File lib/chef/provider/package.rb, line 303 def unlock_package(name, version) raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :unlock" ) end
# File lib/chef/provider/package.rb, line 279 def upgrade_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :upgrade" end
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.
# File lib/chef/provider/package.rb, line 364 def version_compare(v1, v2) gem_v1 = Gem::Version.new(v1) gem_v2 = Gem::Version.new(v2) gem_v1 <=> gem_v2 end
This method performs a strict equality check between two strings representing version numbers
# File lib/chef/provider/package.rb, line 351 def version_equals?(v1, v2) return false unless v1 && v2 v1 == v2 end
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 378 def version_requirement_satisfied?(current_version, new_version) target_version_already_installed?(current_version, new_version) end
Private Instance Methods
# File lib/chef/provider/package.rb, line 679 def add_timeout_option(command_args) # this is deprecated but its not quite done yet #Chef.deprecated(:package_misc, "shell_out_with_timeout and add_timeout_option are deprecated methods, use shell_out_compact_timeout instead") args = command_args.dup if args.last.is_a?(Hash) options = args.pop.dup options[:timeout] = new_resource.timeout if new_resource.timeout options[:timeout] = 900 unless options.key?(:timeout) args << options else args << { timeout: new_resource.timeout ? new_resource.timeout : 900 } end args end
# File lib/chef/provider/package.rb, line 663 def allow_downgrade if new_resource.respond_to?("allow_downgrade") new_resource.allow_downgrade else false end end
@return [Array] #candidate_version(s) as an array
# File lib/chef/provider/package.rb, line 603 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
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 559 def candidates_exist_for_all_forced_changes? forced_packages_missing_candidates.empty? end
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 535 def candidates_exist_for_all_uninstalled? packages_missing_candidates.empty? end
@todo: extract apt/dpkg specific preseeding to a helper class
# File lib/chef/provider/package.rb, line 659 def cookbook_file_available?(path) run_context.has_cookbook_file_in_cookbook?(new_resource.cookbook_name, path) end
@return [Array] current_version(s) as an array
# File lib/chef/provider/package.rb, line 610 def current_version_array @current_version_array ||= [ current_resource.version ].flatten end
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 583 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
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 566 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
# File lib/chef/provider/package.rb, line 108 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
@return [Boolean] if we're doing a multipackage install or not
# File lib/chef/provider/package.rb, line 593 def multipackage? @multipackage_bool ||= new_resource.package_name.is_a?(Array) end
@return [Array] new_version(s) as an array
# File lib/chef/provider/package.rb, line 615 def new_version_array @new_version_array ||= [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v } end
@return [Array] package_name(s) as an array
# File lib/chef/provider/package.rb, line 598 def package_name_array @package_name_array ||= [ new_resource.package_name ].flatten end
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 437 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
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 542 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
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 637 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
# File lib/chef/provider/package.rb, line 671 def shell_out_with_timeout(*command_args) shell_out(*add_timeout_option(command_args)) end
# File lib/chef/provider/package.rb, line 675 def shell_out_with_timeout!(*command_args) shell_out!(*add_timeout_option(command_args)) end
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 623 def source_array @source_array ||= begin if new_resource.source.nil? package_name_array.map { nil } else [ new_resource.source ].flatten end end end
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 473 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 version_compare(current_version, candidate_version) == 1 && !allow_downgrade 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) 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
@todo: extract apt/dpkg specific preseeding to a helper class
# File lib/chef/provider/package.rb, line 654 def template_available?(path) run_context.has_template_in_cookbook?(new_resource.cookbook_name, path) end
# File lib/chef/provider/package.rb, line 135 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
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 457 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