diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb index 0c0ac2ada..0b8c28aa0 100644 --- a/lib/puppet/provider/package/yum.rb +++ b/lib/puppet/provider/package/yum.rb @@ -1,291 +1,290 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do desc "Support via `yum`. Using this provider's `uninstallable` feature will not remove dependent packages. To remove dependent packages with this provider use the `purgeable` feature, but note this feature is destructive and should be used with the utmost care. This provider supports the `install_options` attribute, which allows command-line flags to be passed to yum. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :install_options, :versionable, :virtual_packages commands :yum => "yum", :rpm => "rpm" if command('rpm') confine :true => begin rpm('--version') rescue Puppet::ExecutionFailure false else true end end defaultfor :osfamily => :redhat def self.prefetch(packages) raise Puppet::Error, "The yum provider can only be used as root" if Process.euid != 0 super end # Retrieve the latest package version information for a given package name # and combination of repos to enable and disable. # # @note If multiple package versions are defined (such as in the case where a # package is built for multiple architectures), the first package found # will be used. # # @api private # @param package [String] The name of the package to query # @param enablerepo [Array] A list of repositories to enable for this query # @param disablerepo [Array] A list of repositories to disable for this query # @param disableexcludes [Array] A list of repository excludes to disable for this query # @return [Hash] def self.latest_package_version(package, enablerepo, disablerepo, disableexcludes) key = [enablerepo, disablerepo, disableexcludes] @latest_versions ||= {} if @latest_versions[key].nil? @latest_versions[key] = check_updates(enablerepo, disablerepo, disableexcludes) end if @latest_versions[key][package] @latest_versions[key][package].first end end # Search for all installed packages that have newer versions, given a # combination of repositories to enable and disable. # # @api private # @param enablerepo [Array] A list of repositories to enable for this query # @param disablerepo [Array] A list of repositories to disable for this query # @param disableexcludes [Array] A list of repository excludes to disable for this query # @return [Hash>>] All packages that were # found with a list of found versions for each package. def self.check_updates(enablerepo, disablerepo, disableexcludes) args = [command(:yum), 'check-update'] args.concat(enablerepo.map { |repo| ["--enablerepo=#{repo}"] }.flatten) args.concat(disablerepo.map { |repo| ["--disablerepo=#{repo}"] }.flatten) args.concat(disableexcludes.map { |repo| ["--disableexcludes=#{repo}"] }.flatten) output = Puppet::Util::Execution.execute(args, :failonfail => false, :combine => false) updates = {} if output.exitstatus == 100 updates = parse_updates(output) elsif output.exitstatus == 0 self.debug "yum check-update exited with 0; no package updates available." else self.warn "Could not check for updates, 'yum check-update' exited with #{output.exitstatus}" end updates end def self.parse_updates(str) # Strip off all content before the first blank line body = str.partition(/^\s*\n/m).last updates = Hash.new { |h, k| h[k] = [] } - body.lines.each do |line| - hash = update_to_hash(line) + body.split.each_slice(3) do |tuple| + hash = update_to_hash(*tuple[0..1]) # Create entries for both the package name without a version and a # version since yum considers those as mostly interchangeable. short_name = hash[:name] long_name = "#{hash[:name]}.#{hash[:arch]}" updates[short_name] << hash updates[long_name] << hash end updates end - def self.update_to_hash(line) - pkgname, pkgversion, *_ = line.split(/\s+/) + def self.update_to_hash(pkgname, pkgversion) name, arch = pkgname.split('.') match = pkgversion.match(/^(?:(\d+):)?(\S+)-(\S+)$/) epoch = match[1] || '0' version = match[2] release = match[3] { :name => name, :epoch => epoch, :version => version, :release => release, :arch => arch, } end def self.clear @latest_versions = nil end def install wanted = @resource[:name] # If not allowing virtual packages, do a query to ensure a real package exists unless @resource.allow_virtual? yum *['-d', '0', '-e', '0', '-y', install_options, :list, wanted].compact end should = @resource.should(:ensure) self.debug "Ensuring => #{should}" operation = :install case should when true, false, Symbol # pass should = nil else # Add the package version wanted += "-#{should}" is = self.query if is && yum_compareEVR(yum_parse_evr(should), yum_parse_evr(is[:ensure])) < 0 self.debug "Downgrading package #{@resource[:name]} from version #{is[:ensure]} to #{should}" operation = :downgrade end end args = ["-d", "0", "-e", "0", "-y", install_options, operation, wanted].compact yum *args # If a version was specified, query again to see if it is a matching version if should is = self.query raise Puppet::Error, "Could not find package #{self.name}" unless is # FIXME: Should we raise an exception even if should == :latest # and yum updated us to a version other than @param_hash[:ensure] ? vercmp_result = yum_compareEVR(yum_parse_evr(should), yum_parse_evr(is[:ensure])) raise Puppet::Error, "Failed to update to version #{should}, got version #{is[:ensure]} instead" if vercmp_result != 0 end end # What's the latest package version available? def latest upd = self.class.latest_package_version(@resource[:name], enablerepo, disablerepo, disableexcludes) unless upd.nil? # FIXME: there could be more than one update for a package # because of multiarch return "#{upd[:epoch]}:#{upd[:version]}-#{upd[:release]}" else # Yum didn't find updates, pretend the current # version is the latest raise Puppet::DevError, "Tried to get latest on a missing package" if properties[:ensure] == :absent return properties[:ensure] end end def update # Install in yum can be used for update, too self.install end def purge yum "-y", :erase, @resource[:name] end # parse a yum "version" specification # this re-implements yum's # rpmUtils.miscutils.stringToVersion() in ruby def yum_parse_evr(s) ei = s.index(':') if ei e = s[0,ei] s = s[ei+1,s.length] else e = nil end e = String(Bignum(e)) rescue '0' ri = s.index('-') if ri v = s[0,ri] r = s[ri+1,s.length] else v = s r = nil end return { :epoch => e, :version => v, :release => r } end # how yum compares two package versions: # rpmUtils.miscutils.compareEVR(), which massages data types and then calls # rpm.labelCompare(), found in rpm.git/python/header-py.c, which # sets epoch to 0 if null, then compares epoch, then ver, then rel # using compare_values() and returns the first non-0 result, else 0. # This function combines the logic of compareEVR() and labelCompare(). # # "version_should" can be v, v-r, or e:v-r. # "version_is" will always be at least v-r, can be e:v-r def yum_compareEVR(should_hash, is_hash) # pass on to rpm labelCompare rc = compare_values(should_hash[:epoch], is_hash[:epoch]) return rc unless rc == 0 rc = compare_values(should_hash[:version], is_hash[:version]) return rc unless rc == 0 # here is our special case, PUP-1244. # if should_hash[:release] is nil (not specified by the user), # and comparisons up to here are equal, return equal. We need to # evaluate to whatever level of detail the user specified, so we # don't end up upgrading or *downgrading* when not intended. # # This should NOT be triggered if we're trying to ensure latest. return 0 if should_hash[:release].nil? rc = compare_values(should_hash[:release], is_hash[:release]) return rc end # this method is a native implementation of the # compare_values function in rpm's python bindings, # found in python/header-py.c, as used by yum. def compare_values(s1, s2) if s1.nil? && s2.nil? return 0 elsif ( not s1.nil? ) && s2.nil? return 1 elsif s1.nil? && (not s2.nil?) return -1 end return rpmvercmp(s1, s2) end private def enablerepo scan_options(resource[:install_options], '--enablerepo') end def disablerepo scan_options(resource[:install_options], '--disablerepo') end def disableexcludes scan_options(resource[:install_options], '--disableexcludes') end # Scan a structure that looks like the package type 'install_options' # structure for all hashes that have a specific key. # # @api private # @param options [Array, nil] The options structure. If the # options are nil an empty array will be returned. # @param key [String] The key to look for in all contained hashes # @return [Array] All hash values with the given key. def scan_options(options, key) return [] if options.nil? options.inject([]) do |repos, opt| if opt.is_a? Hash and opt[key] repos << opt[key] end repos end end end diff --git a/spec/fixtures/unit/provider/package/yum/yum-check-update-multiline.txt b/spec/fixtures/unit/provider/package/yum/yum-check-update-multiline.txt new file mode 100644 index 000000000..11aef971c --- /dev/null +++ b/spec/fixtures/unit/provider/package/yum/yum-check-update-multiline.txt @@ -0,0 +1,201 @@ +Loaded plugins: fastestmirror, security +Loading mirror speeds from cached hostfile + * base: mirrors.usinternet.com + * extras: mirror.itc.virginia.edu + * updates: mirrors.usinternet.com + +abrt.x86_64 2.0.8-21.el6.centos base +abrt-addon-ccpp.x86_64 2.0.8-21.el6.centos base +abrt-addon-kerneloops.x86_64 2.0.8-21.el6.centos base +abrt-addon-python.x86_64 2.0.8-21.el6.centos base +abrt-cli.x86_64 2.0.8-21.el6.centos base +abrt-libs.x86_64 2.0.8-21.el6.centos base +abrt-tui.x86_64 2.0.8-21.el6.centos base +atk.x86_64 1.30.0-1.el6 base +audit.x86_64 2.2-4.el6_5 updates +audit-libs.x86_64 2.2-4.el6_5 updates +augeas-libs.x86_64 1.0.0-5.el6_5.1 updates +bash.x86_64 4.1.2-15.el6_4 base +bfa-firmware.noarch 3.2.21.1-2.el6 base +bind-libs.x86_64 32:9.8.2-0.23.rc1.el6_5.1 updates +bind-utils.x86_64 32:9.8.2-0.23.rc1.el6_5.1 updates +biosdevname.x86_64 0.5.0-2.el6 base +btparser.x86_64 0.17-2.el6 base +busybox.x86_64 1:1.15.1-20.el6 base +centos-release.x86_64 6-5.el6.centos.11.2 updates +chkconfig.x86_64 1.3.49.3-2.el6_4.1 base +coreutils.x86_64 8.4-31.el6_5.1 updates +coreutils-libs.x86_64 8.4-31.el6_5.1 updates +cpp.x86_64 4.4.7-4.el6 base +cpuspeed.x86_64 1:1.5-20.el6_4 base +cronie.x86_64 1.4.4-12.el6 base +cronie-anacron.x86_64 1.4.4-12.el6 base +cups-libs.x86_64 1:1.4.2-50.el6_4.5 base +curl.x86_64 7.19.7-37.el6_5.3 updates +db4.x86_64 4.7.25-18.el6_4 base +db4-utils.x86_64 4.7.25-18.el6_4 base +dbus-glib.x86_64 0.86-6.el6 base +device-mapper.x86_64 1.02.79-8.el6 base +device-mapper-event.x86_64 1.02.79-8.el6 base +device-mapper-event-libs.x86_64 1.02.79-8.el6 base +device-mapper-libs.x86_64 1.02.79-8.el6 base +device-mapper-persistent-data.x86_64 0.2.8-4.el6_5 updates +dhclient.x86_64 12:4.1.1-38.P1.el6.centos base +dhcp-common.x86_64 12:4.1.1-38.P1.el6.centos base +dmidecode.x86_64 1:2.12-5.el6_5 updates +dracut.noarch 004-336.el6_5.2 updates +dracut-kernel.noarch 004-336.el6_5.2 updates +e2fsprogs.x86_64 1.41.12-18.el6 base +e2fsprogs-libs.x86_64 1.41.12-18.el6 base +efibootmgr.x86_64 0.5.4-11.el6 base +ethtool.x86_64 2:3.5-1.4.el6_5 updates +facter.x86_64 1:2.0.2-1.el6 puppetlabs-products +gcc.x86_64 4.4.7-4.el6 base +gcc-c++.x86_64 4.4.7-4.el6 base +glib2.x86_64 2.26.1-7.el6_5 updates +glibc.x86_64 2.12-1.132.el6_5.2 updates +glibc-common.x86_64 2.12-1.132.el6_5.2 updates +glibc-devel.x86_64 2.12-1.132.el6_5.2 updates +glibc-headers.x86_64 2.12-1.132.el6_5.2 updates +gnupg2.x86_64 2.0.14-6.el6_4 base +gnutls.x86_64 2.8.5-14.el6_5 updates +grep.x86_64 2.6.3-4.el6_5.1 updates +grub.x86_64 1:0.97-83.el6 base +grubby.x86_64 7.0.15-5.el6 base +gzip.x86_64 1.3.12-19.el6_4 base +hdparm.x86_64 9.43-4.el6 base +hiera.noarch 1.3.4-1.el6 puppetlabs-products +hwdata.noarch 0.233-9.1.el6 base +initscripts.x86_64 9.03.40-2.el6.centos.1 updates +iproute.x86_64 2.6.32-32.el6_5 updates +iptables.x86_64 1.4.7-11.el6 base +iptables-ipv6.x86_64 1.4.7-11.el6 base +iputils.x86_64 20071127-17.el6_4.2 base +irqbalance.x86_64 2:1.0.4-9.el6_5 updates +iw.x86_64 3.10-1.1.el6 base +kernel.x86_64 2.6.32-431.17.1.el6 updates +kernel-devel.x86_64 2.6.32-431.17.1.el6 updates +kernel-firmware.noarch 2.6.32-431.17.1.el6 updates +kernel-headers.x86_64 2.6.32-431.17.1.el6 updates +kexec-tools.x86_64 2.0.0-273.el6 base +kpartx.x86_64 0.4.9-72.el6_5.2 updates +krb5-devel.x86_64 1.10.3-15.el6_5.1 updates +krb5-libs.x86_64 1.10.3-15.el6_5.1 updates +ledmon.x86_64 0.78-1.el6 base +libblkid.x86_64 2.17.2-12.14.el6_5 updates +libcom_err.x86_64 1.41.12-18.el6 base +libcom_err-devel.x86_64 1.41.12-18.el6 base +libcurl.x86_64 7.19.7-37.el6_5.3 updates +libdrm.x86_64 2.4.45-2.el6 base +libgcc.x86_64 4.4.7-4.el6 base +libgcrypt.x86_64 1.4.5-11.el6_4 base +libgomp.x86_64 4.4.7-4.el6 base +libjpeg-turbo.x86_64 1.2.1-3.el6_5 updates +libnl.x86_64 1.1.4-2.el6 base +libpcap.x86_64 14:1.4.0-1.20130826git2dbcaa1.el6 + base +libproxy.x86_64 0.3.0-4.el6_3 base +libproxy-bin.x86_64 0.3.0-4.el6_3 base +libproxy-python.x86_64 0.3.0-4.el6_3 base +libreport.x86_64 2.0.9-19.el6.centos base +libreport-cli.x86_64 2.0.9-19.el6.centos base +libreport-compat.x86_64 2.0.9-19.el6.centos base +libreport-plugin-kerneloops.x86_64 2.0.9-19.el6.centos base +libreport-plugin-logger.x86_64 2.0.9-19.el6.centos base +libreport-plugin-mailx.x86_64 2.0.9-19.el6.centos base +libreport-plugin-reportuploader.x86_64 2.0.9-19.el6.centos base +libreport-plugin-rhtsupport.x86_64 2.0.9-19.el6.centos base +libreport-python.x86_64 2.0.9-19.el6.centos base +libselinux.x86_64 2.0.94-5.3.el6_4.1 base +libselinux-devel.x86_64 2.0.94-5.3.el6_4.1 base +libselinux-ruby.x86_64 2.0.94-5.3.el6_4.1 base +libselinux-utils.x86_64 2.0.94-5.3.el6_4.1 base +libss.x86_64 1.41.12-18.el6 base +libstdc++.x86_64 4.4.7-4.el6 base +libstdc++-devel.x86_64 4.4.7-4.el6 base +libtar.x86_64 1.2.11-17.el6_4.1 base +libtasn1.x86_64 2.3-6.el6_5 updates +libtiff.x86_64 3.9.4-10.el6_5 updates +libudev.x86_64 147-2.51.el6 base +libuuid.x86_64 2.17.2-12.14.el6_5 updates +libxml2.x86_64 2.7.6-14.el6_5.1 updates +libxml2-python.x86_64 2.7.6-14.el6_5.1 updates +logrotate.x86_64 3.7.8-17.el6 base +lvm2.x86_64 2.02.100-8.el6 base +lvm2-libs.x86_64 2.02.100-8.el6 base +mailx.x86_64 12.4-7.el6 base +man-pages-overrides.noarch 6.5.3-1.el6_5 updates +mdadm.x86_64 3.2.6-7.el6_5.2 updates +microcode_ctl.x86_64 1:1.17-17.el6 base +module-init-tools.x86_64 3.9-21.el6_4 base +mysql-libs.x86_64 5.1.73-3.el6_5 updates +ntp.x86_64 4.2.6p5-1.el6.centos base +ntpdate.x86_64 4.2.6p5-1.el6.centos base +ntsysv.x86_64 1.3.49.3-2.el6_4.1 base +numactl.x86_64 2.0.7-8.el6 base +openldap.x86_64 2.4.23-34.el6_5.1 updates +openssh.x86_64 5.3p1-94.el6 base +openssh-clients.x86_64 5.3p1-94.el6 base +openssh-server.x86_64 5.3p1-94.el6 base +openssl.x86_64 1.0.1e-16.el6_5.14 updates +openssl-devel.x86_64 1.0.1e-16.el6_5.14 updates +pam.x86_64 1.1.1-17.el6 base +parted.x86_64 2.1-21.el6 base +perl.x86_64 4:5.10.1-136.el6 base +perl-Module-Pluggable.x86_64 1:3.90-136.el6 base +perl-Pod-Escapes.x86_64 1:1.04-136.el6 base +perl-Pod-Simple.x86_64 1:3.13-136.el6 base +perl-libs.x86_64 4:5.10.1-136.el6 base +perl-version.x86_64 3:0.77-136.el6 base +pixman.x86_64 0.26.2-5.1.el6_5 updates +pm-utils.x86_64 1.2.5-10.el6_5.1 updates +policycoreutils.x86_64 2.0.83-19.39.el6 base +polkit.x86_64 0.96-5.el6_4 base +postfix.x86_64 2:2.6.6-6.el6_5 updates +prelink.x86_64 0.4.6-3.1.el6_4 base +psmisc.x86_64 22.6-19.el6_5 updates +python.x86_64 2.6.6-52.el6 updates +python-ethtool.x86_64 0.6-5.el6 base +python-libs.x86_64 2.6.6-52.el6 updates +python-urlgrabber.noarch 3.9.1-9.el6 base +ql2400-firmware.noarch 7.00.01-1.el6 base +ql2500-firmware.noarch 7.00.01-1.el6 base +quota.x86_64 1:3.17-21.el6_5 updates +readahead.x86_64 1:1.5.6-2.el6 base +rpm.x86_64 4.8.0-37.el6 base +rpm-libs.x86_64 4.8.0-37.el6 base +rpm-python.x86_64 4.8.0-37.el6 base +rsync.x86_64 3.0.6-9.el6_4.1 base +rsyslog.x86_64 5.8.10-8.el6 base +ruby.x86_64 1.8.7.352-13.el6 updates +ruby-augeas.x86_64 0.4.1-3.el6 puppetlabs-deps +ruby-devel.x86_64 1.8.7.352-13.el6 updates +ruby-irb.x86_64 1.8.7.352-13.el6 updates +ruby-libs.x86_64 1.8.7.352-13.el6 updates +ruby-rdoc.x86_64 1.8.7.352-13.el6 updates +ruby-shadow.x86_64 1:2.2.0-2.el6 puppetlabs-deps +rubygem-json.x86_64 1.5.5-1.el6 puppetlabs-deps +rubygems.noarch 1.3.7-5.el6 base +scl-utils.x86_64 20120927-8.el6 base +selinux-policy.noarch 3.7.19-231.el6_5.3 updates +selinux-policy-targeted.noarch 3.7.19-231.el6_5.3 updates +setup.noarch 2.8.14-20.el6_4.1 base +setuptool.x86_64 1.19.9-4.el6 base +sg3_utils-libs.x86_64 1.28-5.el6 base +sos.noarch 2.2-47.el6.centos.1 updates +sudo.x86_64 1.8.6p3-12.el6 base +sysstat.x86_64 9.0.4-22.el6 base +systemtap-runtime.x86_64 2.3-4.el6_5 updates +sysvinit-tools.x86_64 2.87-5.dsf.el6 base +tzdata.noarch 2014d-1.el6 updates +udev.x86_64 147-2.51.el6 base +upstart.x86_64 0.6.5-13.el6_5.3 updates +util-linux-ng.x86_64 2.17.2-12.14.el6_5 updates +wget.x86_64 1.12-1.11.el6_5 updates +xmlrpc-c.x86_64 1.16.24-1210.1840.el6 base +xmlrpc-c-client.x86_64 1.16.24-1210.1840.el6 base +xorg-x11-drv-ati-firmware.noarch 7.1.0-3.el6 base +yum.noarch 3.2.29-43.el6.centos updates +yum-plugin-fastestmirror.noarch 1.1.30-17.el6_5 updates +yum-plugin-security.noarch 1.1.30-17.el6_5 updates +yum-utils.noarch 1.1.30-17.el6_5 updates diff --git a/spec/fixtures/unit/provider/package/yum/yum-check-update-simple.txt b/spec/fixtures/unit/provider/package/yum/yum-check-update-simple.txt new file mode 100644 index 000000000..55fb46dd3 --- /dev/null +++ b/spec/fixtures/unit/provider/package/yum/yum-check-update-simple.txt @@ -0,0 +1,12 @@ +Loaded plugins: fastestmirror +Determining fastest mirrors + * base: centos.sonn.com + * epel: ftp.osuosl.org + * extras: mirror.web-ster.com + * updates: centos.sonn.com + +curl.i686 7.32.0-10.fc20 updates +curl.x86_64 7.32.0-10.fc20 updates +gawk.i686 4.1.0-3.fc20 updates +dhclient.i686 12:4.1.1-38.P1.fc20 updates +selinux-policy.noarch 3.12.1-163.fc20 updates-testing diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb index 2a4545cbe..fa3a20823 100755 --- a/spec/unit/provider/package/yum_spec.rb +++ b/spec/unit/provider/package/yum_spec.rb @@ -1,505 +1,502 @@ #! /usr/bin/env ruby require 'spec_helper' provider_class = Puppet::Type.type(:package).provider(:yum) describe provider_class do + include PuppetSpec::Fixtures + let(:name) { 'mypackage' } let(:resource) do Puppet::Type.type(:package).new( :name => name, :ensure => :installed, :provider => 'yum' ) end let(:provider) do provider = provider_class.new provider.resource = resource provider end before do provider.stubs(:yum).returns 'yum' provider.stubs(:rpm).returns 'rpm' provider.stubs(:get).with(:version).returns '1' provider.stubs(:get).with(:release).returns '1' provider.stubs(:get).with(:arch).returns 'i386' end describe 'provider features' do it { is_expected.to be_versionable } it { is_expected.to be_install_options } it { is_expected.to be_virtual_packages } end # provider should repond to the following methods [:install, :latest, :update, :purge, :install_options].each do |method| it "should have a(n) #{method}" do expect(provider).to respond_to(method) end end describe 'package evr parsing' do it 'should parse full simple evr' do v = provider.yum_parse_evr('0:1.2.3-4.el5') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('4.el5') end it 'should parse version only' do v = provider.yum_parse_evr('1.2.3') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq(nil) end it 'should parse version-release' do v = provider.yum_parse_evr('1.2.3-4.5.el6') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('4.5.el6') end it 'should parse release with git hash' do v = provider.yum_parse_evr('1.2.3-4.1234aefd') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('4.1234aefd') end it 'should parse single integer versions' do v = provider.yum_parse_evr('12345') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('12345') expect(v[:release]).to eq(nil) end it 'should parse text in the epoch to 0' do v = provider.yum_parse_evr('foo0:1.2.3-4') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('4') end it 'should parse revisions with text' do v = provider.yum_parse_evr('1.2.3-SNAPSHOT20140107') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('SNAPSHOT20140107') end # test cases for PUP-682 it 'should parse revisions with text and numbers' do v = provider.yum_parse_evr('2.2-SNAPSHOT20121119105647') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('2.2') expect(v[:release]).to eq('SNAPSHOT20121119105647') end end describe 'yum evr comparison' do # currently passing tests it 'should evaluate identical version-release as equal' do v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => '1.el5'}, {:epoch => '0', :version => '1.2.3', :release => '1.el5'}) expect(v).to eq(0) end it 'should evaluate identical version as equal' do v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => nil}, {:epoch => '0', :version => '1.2.3', :release => nil}) expect(v).to eq(0) end it 'should evaluate identical version but older release as less' do v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => '1.el5'}, {:epoch => '0', :version => '1.2.3', :release => '2.el5'}) expect(v).to eq(-1) end it 'should evaluate identical version but newer release as greater' do v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => '3.el5'}, {:epoch => '0', :version => '1.2.3', :release => '2.el5'}) expect(v).to eq(1) end it 'should evaluate a newer epoch as greater' do v = provider.yum_compareEVR({:epoch => '1', :version => '1.2.3', :release => '4.5'}, {:epoch => '0', :version => '1.2.3', :release => '4.5'}) expect(v).to eq(1) end # these tests describe PUP-1244 logic yet to be implemented it 'should evaluate any version as equal to the same version followed by release' do v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => nil}, {:epoch => '0', :version => '1.2.3', :release => '2.el5'}) expect(v).to eq(0) end # test cases for PUP-682 it 'should evaluate same-length numeric revisions numerically' do expect(provider.yum_compareEVR({:epoch => '0', :version => '2.2', :release => '405'}, {:epoch => '0', :version => '2.2', :release => '406'})).to eq(-1) end end describe 'yum version segment comparison' do it 'should treat two nil values as equal' do v = provider.compare_values(nil, nil) expect(v).to eq(0) end it 'should treat a nil value as less than a non-nil value' do v = provider.compare_values(nil, '0') expect(v).to eq(-1) end it 'should treat a non-nil value as greater than a nil value' do v = provider.compare_values('0', nil) expect(v).to eq(1) end it 'should pass two non-nil values on to rpmvercmp' do provider.stubs(:rpmvercmp) { 0 } provider.expects(:rpmvercmp).with('s1', 's2') provider.compare_values('s1', 's2') end end describe 'when installing' do before(:each) do Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm") provider.stubs(:which).with("rpm").returns("/bin/rpm") Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], {:combine => true, :custom_environment => {}, :failonfail => true}).returns("4.10.1\n").at_most_once end it 'should call yum install for :installed' do resource.stubs(:should).with(:ensure).returns :installed provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :install, name) provider.install end it 'should use :install to update' do provider.expects(:install) provider.update end it 'should be able to set version' do version = '1.2' resource[:ensure] = version provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :install, "#{name}-#{version}") provider.stubs(:query).returns :ensure => version provider.install end it 'should handle partial versions specified' do version = '1.3.4' resource[:ensure] = version provider.stubs(:query).returns :ensure => '1.3.4-1.el6' provider.install end it 'should be able to downgrade' do current_version = '1.2' version = '1.0' resource[:ensure] = '1.0' provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :downgrade, "#{name}-#{version}") provider.stubs(:query).returns(:ensure => current_version).then.returns(:ensure => version) provider.install end it 'should accept install options' do resource[:ensure] = :installed resource[:install_options] = ['-t', {'-x' => 'expackage'}] provider.expects(:yum).with('-d', '0', '-e', '0', '-y', ['-t', '-x=expackage'], :install, name) provider.install end it 'allow virtual packages' do resource[:ensure] = :installed resource[:allow_virtual] = true provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :list, name).never provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :install, name) provider.install end end describe 'when uninstalling' do it 'should use erase to purge' do provider.expects(:yum).with('-y', :erase, name) provider.purge end end it 'should be versionable' do expect(provider).to be_versionable end describe 'determining the latest version available for a package' do it "passes the value of enablerepo install_options when querying" do resource[:install_options] = [ {'--enablerepo' => 'contrib'}, {'--enablerepo' => 'centosplus'}, ] provider.stubs(:properties).returns({:ensure => '3.4.5'}) described_class.expects(:latest_package_version).with(name, ['contrib', 'centosplus'], [], []) provider.latest end it "passes the value of disablerepo install_options when querying" do resource[:install_options] = [ {'--disablerepo' => 'updates'}, {'--disablerepo' => 'centosplus'}, ] provider.stubs(:properties).returns({:ensure => '3.4.5'}) described_class.expects(:latest_package_version).with(name, [], ['updates', 'centosplus'], []) provider.latest end it "passes the value of disableexcludes install_options when querying" do resource[:install_options] = [ {'--disableexcludes' => 'main'}, {'--disableexcludes' => 'centosplus'}, ] provider.stubs(:properties).returns({:ensure => '3.4.5'}) described_class.expects(:latest_package_version).with(name, [], [], ['main', 'centosplus']) provider.latest end describe 'and a newer version is not available' do before :each do described_class.stubs(:latest_package_version).with(name, [], [], []).returns nil end it 'raises an error the package is not installed' do provider.stubs(:properties).returns({:ensure => :absent}) expect { provider.latest }.to raise_error(Puppet::DevError, 'Tried to get latest on a missing package') end it 'returns version of the currently installed package' do provider.stubs(:properties).returns({:ensure => '3.4.5'}) expect(provider.latest).to eq('3.4.5') end end describe 'and a newer version is available' do let(:latest_version) do { :name => name, :epoch => '1', :version => '2.3.4', :release => '5', :arch => 'i686', } end it 'includes the epoch in the version string' do described_class.stubs(:latest_package_version).with(name, [], [], []).returns(latest_version) expect(provider.latest).to eq('1:2.3.4-5') end end end describe "lazy loading of latest package versions" do before { described_class.clear } after { described_class.clear } let(:mypackage_version) do { :name => name, :epoch => '1', :version => '2.3.4', :release => '5', :arch => 'i686', } end let(:mypackage_newerversion) do { :name => name, :epoch => '1', :version => '4.5.6', :release => '7', :arch => 'i686', } end let(:latest_versions) { {name => [mypackage_version]} } let(:enabled_versions) { {name => [mypackage_newerversion]} } it "returns the version hash if the package was found" do described_class.expects(:check_updates).with([], [], []).once.returns(latest_versions) version = described_class.latest_package_version(name, [], [], []) expect(version).to eq(mypackage_version) end it "is nil if the package was not found in the query" do described_class.expects(:check_updates).with([], [], []).once.returns(latest_versions) version = described_class.latest_package_version('nopackage', [], [], []) expect(version).to be_nil end it "caches the package list and reuses that for subsequent queries" do described_class.expects(:check_updates).with([], [], []).once.returns(latest_versions) 2.times { version = described_class.latest_package_version(name, [], [], []) expect(version).to eq mypackage_version } end it "caches separate lists for each combination of 'enablerepo' and 'disablerepo' and 'disableexcludes'" do described_class.expects(:check_updates).with([], [], []).once.returns(latest_versions) described_class.expects(:check_updates).with(['enabled'], ['disabled'], ['disableexcludes']).once.returns(enabled_versions) 2.times { version = described_class.latest_package_version(name, [], [], []) expect(version).to eq mypackage_version } 2.times { version = described_class.latest_package_version(name, ['enabled'], ['disabled'], ['disableexcludes']) expect(version).to eq(mypackage_newerversion) } end end describe "executing yum check-update" do before do described_class.stubs(:command).with(:yum).returns '/usr/bin/yum' end it "passes repos to enable to 'yum check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %w[/usr/bin/yum check-update --enablerepo=updates --enablerepo=centosplus] end.returns(stub(:exitstatus => 0)) described_class.check_updates(%w[updates centosplus], [], []) end it "passes repos to disable to 'yum check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %w[/usr/bin/yum check-update --disablerepo=updates --disablerepo=centosplus] end.returns(stub(:exitstatus => 0)) described_class.check_updates([],%w[updates centosplus], []) end it "passes a combination of repos to enable and disable to 'yum check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %w[/usr/bin/yum check-update --enablerepo=os --enablerepo=contrib --disablerepo=updates --disablerepo=centosplus] end.returns(stub(:exitstatus => 0)) described_class.check_updates(%w[os contrib], %w[updates centosplus], []) end it "passes disableexcludes to 'yum check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %w[/usr/bin/yum check-update --disableexcludes=main --disableexcludes=centosplus] end.returns(stub(:exitstatus => 0)) described_class.check_updates([], [], %w[main centosplus]) end it "passes all options to 'yum check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %w[/usr/bin/yum check-update --enablerepo=a --enablerepo=b --disablerepo=c --disablerepo=d --disableexcludes=e --disableexcludes=f] end.returns(stub(:exitstatus => 0)) described_class.check_updates(%w[a b], %w[c d], %w[e f]) end it "returns an empty hash if 'yum check-update' returned 0" do Puppet::Util::Execution.expects(:execute).returns(stub :exitstatus => 0) expect(described_class.check_updates([], [], [])).to be_empty end it "returns a populated hash if 'yum check-update returned 100'" do output = stub(:exitstatus => 100) Puppet::Util::Execution.expects(:execute).returns(output) described_class.expects(:parse_updates).with(output).returns({:has => :updates}) expect(described_class.check_updates([], [], [])).to eq({:has => :updates}) end it "returns an empty hash if 'yum check-update' returned an exit code that was not 0 or 100" do Puppet::Util::Execution.expects(:execute).returns(stub(:exitstatus => 1)) described_class.expects(:warn) expect(described_class.check_updates([], [], [])).to eq({}) end end describe "parsing the output of check-update" do - let(:check_update) do - # Trailing whitespace is intentional - <<-EOD -Loaded plugins: fastestmirror -Determining fastest mirrors - * base: centos.sonn.com - * epel: ftp.osuosl.org - * extras: mirror.web-ster.com - * updates: centos.sonn.com - -curl.i686 7.32.0-10.fc20 updates -curl.x86_64 7.32.0-10.fc20 updates -gawk.i686 4.1.0-3.fc20 updates -dhclient.i686 12:4.1.1-38.P1.fc20 updates -selinux-policy.noarch 3.12.1-163.fc20 updates-testing - EOD - end - - it 'creates an entry for each package keyed on the package name' do - output = described_class.parse_updates(check_update) - expect(output['curl']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'i686'}, {:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'x86_64'}]) - expect(output['gawk']).to eq([{:name => 'gawk', :epoch => '0', :version => '4.1.0', :release => '3.fc20', :arch => 'i686'}]) - expect(output['dhclient']).to eq([{:name => 'dhclient', :epoch => '12', :version => '4.1.1', :release => '38.P1.fc20', :arch => 'i686'}]) - expect(output['selinux-policy']).to eq([{:name => 'selinux-policy', :epoch => '0', :version => '3.12.1', :release => '163.fc20', :arch => 'noarch'}]) - end - - it 'creates an entry for each package keyed on the package name and package architecture' do - output = described_class.parse_updates(check_update) - expect(output['curl.i686']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'i686'}]) - expect(output['curl.x86_64']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'x86_64'}]) - expect(output['gawk.i686']).to eq([{:name => 'gawk', :epoch => '0', :version => '4.1.0', :release => '3.fc20', :arch => 'i686'}]) - expect(output['dhclient.i686']).to eq([{:name => 'dhclient', :epoch => '12', :version => '4.1.1', :release => '38.P1.fc20', :arch => 'i686'}]) - expect(output['selinux-policy.noarch']).to eq([{:name => 'selinux-policy', :epoch => '0', :version => '3.12.1', :release => '163.fc20', :arch => 'noarch'}]) + + describe "with no multiline entries" do + let(:check_update) { File.read(my_fixture('yum-check-update-simple.txt')) } + let(:output) { described_class.parse_updates(check_update) } + + it 'creates an entry for each package keyed on the package name' do + expect(output['curl']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'i686'}, {:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'x86_64'}]) + expect(output['gawk']).to eq([{:name => 'gawk', :epoch => '0', :version => '4.1.0', :release => '3.fc20', :arch => 'i686'}]) + expect(output['dhclient']).to eq([{:name => 'dhclient', :epoch => '12', :version => '4.1.1', :release => '38.P1.fc20', :arch => 'i686'}]) + expect(output['selinux-policy']).to eq([{:name => 'selinux-policy', :epoch => '0', :version => '3.12.1', :release => '163.fc20', :arch => 'noarch'}]) + end + + it 'creates an entry for each package keyed on the package name and package architecture' do + expect(output['curl.i686']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'i686'}]) + expect(output['curl.x86_64']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'x86_64'}]) + expect(output['gawk.i686']).to eq([{:name => 'gawk', :epoch => '0', :version => '4.1.0', :release => '3.fc20', :arch => 'i686'}]) + expect(output['dhclient.i686']).to eq([{:name => 'dhclient', :epoch => '12', :version => '4.1.1', :release => '38.P1.fc20', :arch => 'i686'}]) + expect(output['selinux-policy.noarch']).to eq([{:name => 'selinux-policy', :epoch => '0', :version => '3.12.1', :release => '163.fc20', :arch => 'noarch'}]) + end + end + + describe "with multiline entries" do + let(:check_update) { File.read(my_fixture('yum-check-update-multiline.txt')) } + let(:output) { described_class.parse_updates(check_update) } + + it "parses multi-line values as a single package tuple" do + expect(output['libpcap']).to eq([{:name => 'libpcap', :epoch => '14', :version => '1.4.0', :release => '1.20130826git2dbcaa1.el6', :arch => 'x86_64'}]) + end end end describe "parsing a line from yum check-update" do it "splits up the package name and architecture fields" do - checkupdate = "curl.i686 7.32.0-10.fc20 updates" + checkupdate = %w[curl.i686 7.32.0-10.fc20] - parsed = described_class.update_to_hash(checkupdate) + parsed = described_class.update_to_hash(*checkupdate) expect(parsed[:name]).to eq 'curl' expect(parsed[:arch]).to eq 'i686' end it "splits up the epoch, version, and release fields" do - checkupdate = "dhclient.i686 12:4.1.1-38.P1.el6.centos base" - parsed = described_class.update_to_hash(checkupdate) + checkupdate = %w[dhclient.i686 12:4.1.1-38.P1.el6.centos] + parsed = described_class.update_to_hash(*checkupdate) expect(parsed[:epoch]).to eq '12' expect(parsed[:version]).to eq '4.1.1' expect(parsed[:release]).to eq '38.P1.el6.centos' end it "sets the epoch to 0 when an epoch is not specified" do - checkupdate = "curl.i686 7.32.0-10.fc20 updates" + checkupdate = %w[curl.i686 7.32.0-10.fc20] - parsed = described_class.update_to_hash(checkupdate) + parsed = described_class.update_to_hash(*checkupdate) expect(parsed[:epoch]).to eq '0' expect(parsed[:version]).to eq '7.32.0' expect(parsed[:release]).to eq '10.fc20' end end end