diff --git a/lib/puppet/provider/package/pkg.rb b/lib/puppet/provider/package/pkg.rb index 260f09f94..89875bdd0 100644 --- a/lib/puppet/provider/package/pkg.rb +++ b/lib/puppet/provider/package/pkg.rb @@ -1,198 +1,245 @@ require 'puppet/provider/package' Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package do desc "OpenSolaris image packaging system. See pkg(5) for more information" # http://docs.oracle.com/cd/E19963-01/html/820-6572/managepkgs.html # A few notes before we start : # Opensolaris pkg has two slightly different formats (as of now.) # The first one is what is distributed with the Solaris 11 Express 11/10 dvd # The latest one is what you get when you update package. # To make things more interesting, pkg version just returns a sha sum. # dvd: pkg version => 052adf36c3f4 # updated: pkg version => 630e1ffc7a19 # Thankfully, solaris has not changed the commands to be used. # TODO: We still have to allow packages to specify a preferred publisher. has_feature :versionable has_feature :upgradable has_feature :holdable commands :pkg => "/usr/bin/pkg" confine :osfamily => :solaris defaultfor :osfamily => :solaris, :kernelrelease => '5.11' def self.instances - pkg(:list, '-H').split("\n").map{|l| new(parse_line(l))} + pkg(:list, '-Hv').split("\n").map{|l| new(parse_line(l))} end # The IFO flag field is just what it names, the first field can have ether # i_nstalled or -, and second field f_rozen or -, and last # o_bsolate or r_rename or - # so this checks if the installed field is present, and also verifies that # if not the field is -, else we dont know what we are doing and exit with # out doing more damage. def self.ifo_flag(flags) ( case flags[0..0] when 'i' {:status => 'installed'} when '-' {:status => 'known'} else raise ArgumentError, 'Unknown format %s: %s[%s]' % [self.name, flags, flags[0..0]] end ).merge( case flags[1..1] when 'f' {:ensure => 'held'} when '-' {} else raise ArgumentError, 'Unknown format %s: %s[%s]' % [self.name, flags, flags[1..1]] end ) end # The UFOXI field is the field present in the older pkg # (solaris 2009.06 - snv151a) # similar to IFO, UFOXI is also an either letter or - # u_pdate indicates that an update for the package is available. # f_rozen(n/i) o_bsolete x_cluded(n/i) i_constrained(n/i) # note that u_pdate flag may not be trustable due to constraints. # so we dont rely on it # Frozen was never implemented in UFOXI so skipping frozen here. def self.ufoxi_flag(flags) {} end # pkg state was present in the older version of pkg (with UFOXI) but is # no longer available with the IFO field version. When it was present, # it was used to indicate that a particular version was present (installed) # and later versions were known. Note that according to the pkg man page, # known never lists older versions of the package. So we can rely on this # field to make sure that if a known is present, then the pkg is upgradable. def self.pkg_state(state) case state when /installed/ {:status => 'installed'} when /known/ {:status => 'known'} else raise ArgumentError, 'Unknown format %s: %s' % [self.name, state] end end # Here is (hopefully) the only place we will have to deal with multiple # formats of output for different pkg versions. def self.parse_line(line) (case line.chomp - # NAME (PUBLISHER) VERSION IFO (new:630e1ffc7a19) - # system/core-os 0.5.11-0.169 i-- - when /^(\S+) +(\S+) +(...)$/ - {:name => $1, :ensure => $2}.merge ifo_flag($3) + # FMRI IFO + # pkg://omnios/SUNWcs@0.5.11,5.11-0.151008:20131204T022241Z --- + when %r'^pkg://([^/]+)/([^@]+)@(\S+) +(...)$' + {:publisher => $1, :name => $2, :ensure => $3}.merge ifo_flag($4) - # x11/wm/fvwm (fvwm.org) 2.6.1-3 i-- - when /^(\S+) \((.+)\) +(\S+) +(...)$/ - {:name => $1, :publisher => $2, :ensure => $3}.merge ifo_flag($4) - - # NAME (PUBLISHER) VERSION STATE UFOXI (dvd:052adf36c3f4) - # SUNWcs 0.5.11-0.126 installed ----- - when /^(\S+) +(\S+) +(\S+) +(.....)$/ - {:name => $1, :ensure => $2}.merge pkg_state($3).merge(ufoxi_flag($4)) - - # web/firefox/plugin/flash (extra) 10.0.32.18-0.111 installed ----- - when /^(\S+) \((.+)\) +(\S+) +(\S+) +(.....)$/ - {:name => $1, :publisher => $2, :ensure => $3}.merge pkg_state($4).merge(ufoxi_flag($5)) + # FMRI STATE UFOXI + # pkg://solaris/SUNWcs@0.5.11,5.11-0.151.0.1:20101105T001108Z installed u---- + when %r'^pkg://([^/]+)/([^@]+)@(\S+) +(\S+) +(.....)$' + {:publisher => $1, :name => $2, :ensure => $3}.merge pkg_state($4).merge(ufoxi_flag($5)) else raise ArgumentError, 'Unknown line format %s: %s' % [self.name, line] end).merge({:provider => self.name}) end def hold pkg(:freeze, @resource[:name]) end def unhold r = exec_cmd(command(:pkg), 'unfreeze', @resource[:name]) raise Puppet::Error, "Unable to unfreeze #{r[:out]}" unless [0,4].include? r[:exit] end + def insync?(is) + # this is called after the generic version matching logic (insync? for the + # type), so we only get here if should != is, and 'should' is a version + # number. 'is' might not be, though. + should = @resource[:ensure] + # NB: it is apparently possible for repository administrators to publish + # packages which do not include build or branch versions, but component + # version must always be present, and the timestamp is added by pkgsend + # publish. + if /^[0-9.]+(,[0-9.]+)?(-[0-9.]+)?:[0-9]+T[0-9]+Z$/ !~ should + # We have a less-than-explicit version string, which we must accept for + # backward compatibility. We can find the real version this would match + # by asking pkg for the all matching versions, and selecting the first + # installable one [0]; this can change over time when remote repositories + # are updated, but the principle of least astonishment should still hold: + # if we allow users to specify less-than-explicit versions, the + # functionality should match that of the package manager. + # + # [0]: we could simply get the newest matching version with 'pkg list + # -n', but that isn't always correct, since it might not be installable. + # If that were the case we could potentially end up returning false for + # insync? here but not actually changing the package version in install + # (ie. if the currently installed version is the latest matching version + # that is installable, we would falsely conclude here that since the + # installed version is not the latest matching version, we're not in + # sync). 'pkg list -a' instead of '-n' would solve this, but + # unfortunately it doesn't consider downgrades 'available' (eg. with + # installed foo@1.0, list -a foo@0.9 would fail). + name = @resource[:name] + potential_matches = pkg(:list, '-Hvfa', "#{name}@#{should}").split("\n").map{|l|self.class.parse_line(l)} + n = potential_matches.length + if n > 1 + warning("Implicit version #{should} has #{n} possible matches") + end + potential_matches.each{ |p| + command = is == :absent ? 'install' : 'update' + status = exec_cmd(command(:pkg), command, '-n', "#{name}@#{p[:ensure]}")[:exit] + case status + when 4 + # if the first installable match would cause no changes, we're in sync + return true + when 0 + warning("Selecting version '#{p[:ensure]}' for implicit '#{should}'") + @resource[:ensure] = p[:ensure] + return false + end + } + raise Puppet::DevError, "No version of #{name} matching #{should} is installable, even though the package is currently installed" + end + + false + end + # Return the version of the package. Note that the bug # http://defect.opensolaris.org/bz/show_bug.cgi?id=19159% # notes that we can't use -Ha for the same even though the manual page reads that way. def latest - lines = pkg(:list, "-Hn", @resource[:name]).split("\n") + lines = pkg(:list, "-Hvn", @resource[:name]).split("\n") # remove certificate expiration warnings from the output, but report them cert_warnings = lines.select { |line| line =~ /^Certificate/ } unless cert_warnings.empty? Puppet.warning("pkg warning: #{cert_warnings.join(', ')}") end lst = lines.select { |line| line !~ /^Certificate/ }.map { |line| self.class.parse_line(line) } # Now we know there is a newer version. But is that installable? (i.e are there any constraints?) # return the first known we find. The only way that is currently available is to do a dry run of # pkg update and see if could get installed (`pkg update -n res`). known = lst.find {|p| p[:status] == 'known' } return known[:ensure] if known and exec_cmd(command(:pkg), 'update', '-n', @resource[:name])[:exit].zero? # If not, then return the installed, else nil (lst.find {|p| p[:status] == 'installed' } || {})[:ensure] end # install the package and accept all licenses. def install(nofail = false) name = @resource[:name] should = @resource[:ensure] # always unhold if explicitly told to install/update self.unhold + is = self.query + if is[:ensure].to_sym == :absent + command = 'install' + else + command = 'update' + end unless should.is_a? Symbol name += "@#{should}" - is = self.query - unless is[:ensure].to_sym == :absent - self.uninstall if Puppet::Util::Package.versioncmp(should, is[:ensure]) < 0 - end end - r = exec_cmd(command(:pkg), 'install', '--accept', name) + r = exec_cmd(command(:pkg), command, '--accept', name) return r if nofail raise Puppet::Error, "Unable to update #{r[:out]}" if r[:exit] != 0 end # uninstall the package. The complication comes from the -r_ecursive flag which is no longer # present in newer package version. def uninstall cmd = [:uninstall] case (pkg :version).chomp when /052adf36c3f4/ cmd << '-r' end cmd << @resource[:name] pkg cmd end # update the package to the latest version available def update r = install(true) # 4 == /No updates available for this image./ return if [0,4].include? r[:exit] raise Puppet::Error, "Unable to update #{r[:out]}" end # list a specific package def query - r = exec_cmd(command(:pkg), 'list', '-H', @resource[:name]) + r = exec_cmd(command(:pkg), 'list', '-Hv', @resource[:name]) return {:ensure => :absent, :name => @resource[:name]} if r[:exit] != 0 self.class.parse_line(r[:out]) end def exec_cmd(*cmd) output = Puppet::Util::Execution.execute(cmd, :failonfail => false, :combine => true) {:out => output, :exit => $CHILD_STATUS.exitstatus} end end diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb index 816c1a0a5..58f065eda 100644 --- a/lib/puppet/type/package.rb +++ b/lib/puppet/type/package.rb @@ -1,500 +1,504 @@ # Define the different packaging systems. Each package system is implemented # in a module, which then gets used to individually extend each package object. # This allows packages to exist on the same machine using different packaging # systems. require 'puppet/parameter/package_options' require 'puppet/parameter/boolean' module Puppet Type.newtype(:package) do @doc = "Manage packages. There is a basic dichotomy in package support right now: Some package types (e.g., yum and apt) can retrieve their own package files, while others (e.g., rpm and sun) cannot. For those package formats that cannot retrieve their own files, you can use the `source` parameter to point to the correct file. Puppet will automatically guess the packaging format that you are using based on the platform you are on, but you can override it using the `provider` parameter; each provider defines what it requires in order to function, and you must meet those requirements to use a given provider. **Autorequires:** If Puppet is managing the files specified as a package's `adminfile`, `responsefile`, or `source`, the package resource will autorequire those files." feature :reinstallable, "The provider can reinstall packages.", :methods => [:reinstall] feature :installable, "The provider can install packages.", :methods => [:install] feature :uninstallable, "The provider can uninstall packages.", :methods => [:uninstall] feature :upgradeable, "The provider can upgrade to the latest version of a package. This feature is used by specifying `latest` as the desired value for the package.", :methods => [:update, :latest] feature :purgeable, "The provider can purge packages. This generally means that all traces of the package are removed, including existing configuration files. This feature is thus destructive and should be used with the utmost care.", :methods => [:purge] feature :versionable, "The provider is capable of interrogating the package database for installed version(s), and can select which out of a set of available versions of a package to install if asked." feature :holdable, "The provider is capable of placing packages on hold such that they are not automatically upgraded as a result of other package dependencies unless explicit action is taken by a user or another package. Held is considered a superset of installed.", :methods => [:hold] feature :install_options, "The provider accepts options to be passed to the installer command." feature :uninstall_options, "The provider accepts options to be passed to the uninstaller command." feature :package_settings, "The provider accepts package_settings to be ensured for the given package. The meaning and format of these settings is provider-specific.", :methods => [:package_settings_insync?, :package_settings, :package_settings=] feature :virtual_packages, "The provider accepts virtual package names for install and uninstall." ensurable do desc <<-EOT What state the package should be in. On packaging systems that can retrieve new packages on their own, you can choose which package to retrieve by specifying a version number or `latest` as the ensure value. On packaging systems that manage configuration files separately from "normal" system files, you can uninstall config files by specifying `purged` as the ensure value. This defaults to `installed`. EOT attr_accessor :latest newvalue(:present, :event => :package_installed) do provider.install end newvalue(:absent, :event => :package_removed) do provider.uninstall end newvalue(:purged, :event => :package_purged, :required_features => :purgeable) do provider.purge end newvalue(:held, :event => :package_held, :required_features => :holdable) do provider.hold end # Alias the 'present' value. aliasvalue(:installed, :present) newvalue(:latest, :required_features => :upgradeable) do # Because yum always exits with a 0 exit code, there's a retrieve # in the "install" method. So, check the current state now, # to compare against later. current = self.retrieve begin provider.update rescue => detail self.fail Puppet::Error, "Could not update: #{detail}", detail end if current == :absent :package_installed else :package_changed end end newvalue(/./, :required_features => :versionable) do begin provider.install rescue => detail self.fail Puppet::Error, "Could not update: #{detail}", detail end if self.retrieve == :absent :package_installed else :package_changed end end defaultto :installed # Override the parent method, because we've got all kinds of # funky definitions of 'in sync'. def insync?(is) @lateststamp ||= (Time.now.to_i - 1000) # Iterate across all of the should values, and see how they # turn out. @should.each { |should| case should when :present return true unless [:absent, :purged, :held].include?(is) when :latest # Short-circuit packages that are not present return false if is == :absent or is == :purged # Don't run 'latest' more than about every 5 minutes if @latest and ((Time.now.to_i - @lateststamp) / 60) < 5 #self.debug "Skipping latest check" else begin @latest = provider.latest @lateststamp = Time.now.to_i rescue => detail error = Puppet::Error.new("Could not get latest version: #{detail}") error.set_backtrace(detail.backtrace) raise error end end case when is.is_a?(Array) && is.include?(@latest) return true when is == @latest return true when is == :present # This will only happen on retarded packaging systems # that can't query versions. return true else self.debug "#{@resource.name} #{is.inspect} is installed, latest is #{@latest.inspect}" end when :absent return true if is == :absent or is == :purged when :purged return true if is == :purged # this handles version number matches and # supports providers that can have multiple versions installed when *Array(is) return true + else + # We have version numbers, and no match. If the provider has + # additional logic, run it here. + return provider.insync?(is) if provider.respond_to?(:insync?) end } false end # This retrieves the current state. LAK: I think this method is unused. def retrieve provider.properties[:ensure] end # Provide a bit more information when logging upgrades. def should_to_s(newvalue = @should) if @latest @latest.to_s else super(newvalue) end end end newparam(:name) do desc "The package name. This is the name that the packaging system uses internally, which is sometimes (especially on Solaris) a name that is basically useless to humans. If you want to abstract package installation, then you can use aliases to provide a common name to packages: # In the 'openssl' class $ssl = $operatingsystem ? { solaris => SMCossl, default => openssl } # It is not an error to set an alias to the same value as the # object name. package { $ssl: ensure => installed, alias => openssl } . etc. . $ssh = $operatingsystem ? { solaris => SMCossh, default => openssh } # Use the alias to specify a dependency, rather than # having another selector to figure it out again. package { $ssh: ensure => installed, alias => openssh, require => Package[openssl] } " isnamevar validate do |value| if !value.is_a?(String) raise ArgumentError, "Name must be a String not #{value.class}" end end end newproperty(:package_settings, :required_features=>:package_settings) do desc "Settings that can change the contents or configuration of a package. The formatting and effects of package_settings are provider-specific; any provider that implements them must explain how to use them in its documentation. (Our general expectation is that if a package is installed but its settings are out of sync, the provider should re-install that package with the desired settings.) An example of how package_settings could be used is FreeBSD's port build options --- a future version of the provider could accept a hash of options, and would reinstall the port if the installed version lacked the correct settings. package { 'www/apache22': package_settings => { 'SUEXEC' => false } } Again, check the documentation of your platform's package provider to see the actual usage." validate do |value| if provider.respond_to?(:package_settings_validate) provider.package_settings_validate(value) else super(value) end end munge do |value| if provider.respond_to?(:package_settings_munge) provider.package_settings_munge(value) else super(value) end end def insync?(is) provider.package_settings_insync?(should, is) end def should_to_s(newvalue) if provider.respond_to?(:package_settings_should_to_s) provider.package_settings_should_to_s(should, newvalue) else super(newvalue) end end def is_to_s(currentvalue) if provider.respond_to?(:package_settings_is_to_s) provider.package_settings_is_to_s(should, currentvalue) else super(currentvalue) end end def change_to_s(currentvalue, newvalue) if provider.respond_to?(:package_settings_change_to_s) provider.package_settings_change_to_s(currentvalue, newvalue) else super(currentvalue,newvalue) end end end newparam(:source) do desc "Where to find the actual package. This must be a local file (or on a network file system) or a URL that your specific packaging type understands; Puppet will not retrieve files for you, although you can manage packages as `file` resources." validate do |value| provider.validate_source(value) end end newparam(:instance) do desc "A read-only parameter set by the package." end newparam(:status) do desc "A read-only parameter set by the package." end newparam(:adminfile) do desc "A file containing package defaults for installing packages. This is currently only used on Solaris. The value will be validated according to system rules, which in the case of Solaris means that it should either be a fully qualified path or it should be in `/var/sadm/install/admin`." end newparam(:responsefile) do desc "A file containing any necessary answers to questions asked by the package. This is currently used on Solaris and Debian. The value will be validated according to system rules, but it should generally be a fully qualified path." end newparam(:configfiles) do desc "Whether configfiles should be kept or replaced. Most packages types do not support this parameter. Defaults to `keep`." defaultto :keep newvalues(:keep, :replace) end newparam(:category) do desc "A read-only parameter set by the package." end newparam(:platform) do desc "A read-only parameter set by the package." end newparam(:root) do desc "A read-only parameter set by the package." end newparam(:vendor) do desc "A read-only parameter set by the package." end newparam(:description) do desc "A read-only parameter set by the package." end newparam(:allowcdrom) do desc "Tells apt to allow cdrom sources in the sources.list file. Normally apt will bail if you try this." newvalues(:true, :false) end newparam(:flavor) do desc "OpenBSD supports 'flavors', which are further specifications for which type of package you want." end newparam(:install_options, :parent => Puppet::Parameter::PackageOptions, :required_features => :install_options) do desc <<-EOT An array of additional options to pass when installing a package. These options are package-specific, and should be documented by the software vendor. One commonly implemented option is `INSTALLDIR`: package { 'mysql': ensure => installed, source => 'N:/packages/mysql-5.5.16-winx64.msi', install_options => [ '/S', { 'INSTALLDIR' => 'C:\\mysql-5.5' } ], } Each option in the array can either be a string or a hash, where each key and value pair are interpreted in a provider specific way. Each option will automatically be quoted when passed to the install command. On Windows, this is the **only** place in Puppet where backslash separators should be used. Note that backslashes in double-quoted strings _must_ be double-escaped and backslashes in single-quoted strings _may_ be double-escaped. EOT end newparam(:uninstall_options, :parent => Puppet::Parameter::PackageOptions, :required_features => :uninstall_options) do desc <<-EOT An array of additional options to pass when uninstalling a package. These options are package-specific, and should be documented by the software vendor. For example: package { 'VMware Tools': ensure => absent, uninstall_options => [ { 'REMOVE' => 'Sync,VSS' } ], } Each option in the array can either be a string or a hash, where each key and value pair are interpreted in a provider specific way. Each option will automatically be quoted when passed to the uninstall command. On Windows, this is the **only** place in Puppet where backslash separators should be used. Note that backslashes in double-quoted strings _must_ be double-escaped and backslashes in single-quoted strings _may_ be double-escaped. EOT end newparam(:allow_virtual, :boolean => true, :parent => Puppet::Parameter::Boolean, :required_features => :virtual_packages) do desc 'Specifies if virtual package names are allowed for install and uninstall.' defaultto true end autorequire(:file) do autos = [] [:responsefile, :adminfile].each { |param| if val = self[param] autos << val end } if source = self[:source] and absolute_path?(source) autos << source end autos end # This only exists for testing. def clear if obj = @parameters[:ensure] obj.latest = nil end end # The 'query' method returns a hash of info if the package # exists and returns nil if it does not. def exists? @provider.get(:ensure) != :absent end def present?(current_values) super && current_values[:ensure] != :purged end # This parameter exists to ensure backwards compatibility is preserved. # See https://github.com/puppetlabs/puppet/pull/2614 for discussion. # If/when a metaparameter for controlling how arbitrary resources respond # to refreshing is created, that will supersede this, and this will be # deprecated. newparam(:reinstall_on_refresh) do desc "Whether this resource should respond to refresh events (via `subscribe`, `notify`, or the `~>` arrow) by reinstalling the package. Only works for providers that support the `reinstallable` feature. This is useful for source-based distributions, where you may want to recompile a package if the build options change. If you use this, be careful of notifying classes when you want to restart services. If the class also contains a refreshable package, doing so could cause unnecessary re-installs. Defaults to `false`." newvalues(:true, :false) defaultto :false end # When a refresh event is triggered, calls reinstall on providers # that support the reinstall_on_refresh parameter. def refresh if provider.reinstallable? && @parameters[:reinstall_on_refresh].value == :true && @parameters[:ensure].value != :purged && @parameters[:ensure].value != :absent && @parameters[:ensure].value != :held provider.reinstall end end end end diff --git a/spec/fixtures/unit/provider/package/pkg/dummy_implicit_version b/spec/fixtures/unit/provider/package/pkg/dummy_implicit_version new file mode 100644 index 000000000..5e1812bba --- /dev/null +++ b/spec/fixtures/unit/provider/package/pkg/dummy_implicit_version @@ -0,0 +1,3 @@ +pkg://solaris/dummy@1.0,5.11-0.151006:20140220T084443Z --- +pkg://solaris/dummy@1.0,5.11-0.151006:20140219T191632Z --- +pkg://solaris/dummy@1.0,5.11-0.151006:20140219T191204Z --- diff --git a/spec/fixtures/unit/provider/package/pkg/dummy_solaris10 b/spec/fixtures/unit/provider/package/pkg/dummy_solaris10 index 12a8998e8..6c09f2230 100644 --- a/spec/fixtures/unit/provider/package/pkg/dummy_solaris10 +++ b/spec/fixtures/unit/provider/package/pkg/dummy_solaris10 @@ -1 +1 @@ -dummy 2.5.5-0.111 installed ----- +pkg://solaris/dummy@2.5.5,5.10-0.111:20131230T130000Z installed ----- diff --git a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.certificate_warning b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.certificate_warning index 83849be03..76c8afccf 100644 --- a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.certificate_warning +++ b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.certificate_warning @@ -1,2 +1,2 @@ Certificate '/var/pkg/ssl/871b4ed0ade09926e6adf95f86bf17535f987684' for publisher 'solarisstudio', needed to access 'https://pkg.oracle.com/solarisstudio/release/', will expire in '29' days. -dummy 1.0.6-0.175.0.0.0.2.537 i-- +pkg://solaris/dummy@1.0.6-0.175.0.0.0.2.537 i-- diff --git a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.ifo.installed b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.ifo.installed index 6c8801968..c245f4662 100644 --- a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.ifo.installed +++ b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.ifo.installed @@ -1 +1 @@ -dummy 1.0.6-0.175.0.0.0.2.537 i-- +pkg://solaris/dummy@1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- diff --git a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.ifo.known b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.ifo.known index 48895c1f6..3f147c6f6 100644 --- a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.ifo.known +++ b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.ifo.known @@ -1 +1 @@ -dummy 1.0.6-0.175.0.0.0.2.537 --- +pkg://solaris/dummy@1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z --- diff --git a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.installed b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.installed index 8724a9041..679985c14 100644 --- a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.installed +++ b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.installed @@ -1 +1 @@ -dummy 1.0.6-0.175.0.0.0.2.537 installed u---- +pkg://solaris/dummy@1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z installed u---- diff --git a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.known b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.known index 935cdb8d8..944043001 100644 --- a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.known +++ b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.known @@ -1 +1 @@ -dummy 1.0.6-0.175.0.0.0.2.537 known u---- +pkg://solaris/dummy@1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z known u---- diff --git a/spec/fixtures/unit/provider/package/pkg/incomplete b/spec/fixtures/unit/provider/package/pkg/incomplete index 24a93d10a..ff0a4a2ae 100644 --- a/spec/fixtures/unit/provider/package/pkg/incomplete +++ b/spec/fixtures/unit/provider/package/pkg/incomplete @@ -1 +1 @@ -dummy 2.5.5-0.111 installed ---- RANDOM_TRASH +pkg://solaris/dummy@2.5.5,5.11-0.111:20131230T130000Z installed ---- RANDOM_TRASH diff --git a/spec/fixtures/unit/provider/package/pkg/publisher b/spec/fixtures/unit/provider/package/pkg/publisher deleted file mode 100644 index 5595bbf37..000000000 --- a/spec/fixtures/unit/provider/package/pkg/publisher +++ /dev/null @@ -1,2 +0,0 @@ -SUNWdummy (solaris) 8.8-0.111 installed ----- -service/network/dummy (solaris) 0.5.11-0.151.0.1 installed ----- diff --git a/spec/fixtures/unit/provider/package/pkg/simple b/spec/fixtures/unit/provider/package/pkg/simple deleted file mode 100644 index f63811b20..000000000 --- a/spec/fixtures/unit/provider/package/pkg/simple +++ /dev/null @@ -1,2 +0,0 @@ -SUNWdummy 2.5.5-0.111 installed ----- -dummy2 9.3.6.1-0.111 installed ----- diff --git a/spec/fixtures/unit/provider/package/pkg/solaris11 b/spec/fixtures/unit/provider/package/pkg/solaris11 index 1f3c9480b..046c1bb8b 100644 --- a/spec/fixtures/unit/provider/package/pkg/solaris11 +++ b/spec/fixtures/unit/provider/package/pkg/solaris11 @@ -1,2 +1,2 @@ -dummy/dummy 3.0-0.175.0.0.0.2.537 i-- -dummy/dummy2 1.8.1.2-0.175.0.0.0.2.537 i-- +pkg://solaris/dummy/dummy@3.0,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/dummy/dummy2@1.8.1.2-0.175.0.0.0.2.537:20131230T130000Z i-- diff --git a/spec/fixtures/unit/provider/package/pkg/unknown_status b/spec/fixtures/unit/provider/package/pkg/unknown_status index 70a645b2e..33b170153 100644 --- a/spec/fixtures/unit/provider/package/pkg/unknown_status +++ b/spec/fixtures/unit/provider/package/pkg/unknown_status @@ -1,12 +1,12 @@ -compress/zip 3.0-0.175.0.0.0.2.537 i-- -archiver/gnu-tar 1.26-0.175.0.0.0.2.537 i-- -compress/bzip2 1.0.6-0.175.0.0.0.2.537 i-- -compress/gzip 1.3.5-0.175.0.0.0.2.537 i-- -compress/p7zip 9.20.1-0.175.0.0.0.2.537 i-- -compress/unzip 6.0-0.175.0.0.0.2.537 x-- -compress/zip 3.0-0.175.0.0.0.2.537 i-- -x11/library/toolkit/libxaw7 1.0.9-0.175.0.0.0.0.1215 i-- -x11/library/toolkit/libxt 1.0.9-0.175.0.0.0.0.1215 i-- -shell/bash 4.1.9-0.175.0.0.0.2.537 i-- -shell/zsh 4.3.12-0.175.0.0.0.2.537 i-- -security/sudo 1.8.1.2-0.175.0.0.0.2.537 i-- +pkg://solaris/compress/zip@3.0,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/archiver/gnu-tar@1.26,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/compress/bzip2@1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/compress/gzip@1.3.5,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/compress/p7zip@9.20.1,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/compress/unzip@6.0,5.11-0.175.0.0.0.2.537:20131230T130000Z x-- +pkg://solaris/compress/zip@3.0,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/x11/library/toolkit/libxaw7@1.0.9,5.11-0.175.0.0.0.0.1215:20131230T130000Z i-- +pkg://solaris/x11/library/toolkit/libxt@1.0.9,5.11-0.175.0.0.0.0.1215:20131230T130000Z i-- +pkg://solaris/shell/bash@4.1.9,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/shell/zsh@4.3.12,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- +pkg://solaris/security/sudo@1.8.1.2,5.11-0.175.0.0.0.2.537:20131230T130000Z i-- diff --git a/spec/unit/provider/package/pkg_spec.rb b/spec/unit/provider/package/pkg_spec.rb index ee0ac7778..865e50c2e 100755 --- a/spec/unit/provider/package/pkg_spec.rb +++ b/spec/unit/provider/package/pkg_spec.rb @@ -1,301 +1,333 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:pkg) do let (:resource) { Puppet::Resource.new(:package, 'dummy', :parameters => {:name => 'dummy', :ensure => :latest}) } let (:provider) { described_class.new(resource) } before :each do described_class.stubs(:command).with(:pkg).returns('/bin/pkg') end def self.it_should_respond_to(*actions) actions.each do |action| it "should respond to :#{action}" do provider.should respond_to(action) end end end it_should_respond_to :install, :uninstall, :update, :query, :latest it "should be versionable" do described_class.should be_versionable end describe "#methods" do context ":pkg_state" do it "should raise error on unknown values" do expect { described_class.pkg_state('extra').should }.to raise_error(ArgumentError, /Unknown format/) end ['known', 'installed'].each do |k| it "should return known values" do described_class.pkg_state(k).should == {:status => k} end end end context ":ifo_flag" do it "should raise error on unknown values" do expect { described_class.ifo_flag('x--').should }.to raise_error(ArgumentError, /Unknown format/) end {'i--' => 'installed', '---'=> 'known'}.each do |k, v| it "should return known values" do described_class.ifo_flag(k).should == {:status => v} end end end context ":parse_line" do it "should raise error on unknown values" do expect { described_class.parse_line('pkg (mypkg) 1.2.3.4 i-- zzz').should }.to raise_error(ArgumentError, /Unknown line format/) end { - 'spkg 0.0.7 i--' => {:name => 'spkg', :ensure => '0.0.7', :status => 'installed', :provider => :pkg}, - 'spkg (me) 0.0.7 i--' => {:name => 'spkg', :ensure => '0.0.7', :status => 'installed', :provider => :pkg, :publisher => 'me'}, - 'spkg (me) 0.0.7 if-' => {:name => 'spkg', :ensure => 'held', :status => 'installed', :provider => :pkg, :publisher => 'me'}, - 'spkg 0.0.7 installed -----' => {:name => 'spkg', :ensure => '0.0.7', :status => 'installed', :provider => :pkg}, - 'spkg (me) 0.0.7 installed -----' => {:name => 'spkg', :ensure => '0.0.7', :status => 'installed', :provider => :pkg, :publisher => 'me'}, + 'pkg://omnios/SUNWcs@0.5.11,5.11-0.151006:20130506T161045Z i--' => {:name => 'SUNWcs', :ensure => '0.5.11,5.11-0.151006:20130506T161045Z', :status => 'installed', :provider => :pkg, :publisher => 'omnios'}, + 'pkg://omnios/incorporation/jeos/illumos-gate@11,5.11-0.151006:20130506T183443Z if-' => {:name => 'incorporation/jeos/illumos-gate', :ensure => 'held', :status => 'installed', :provider => :pkg, :publisher => 'omnios'}, + 'pkg://solaris/SUNWcs@0.5.11,5.11-0.151.0.1:20101105T001108Z installed -----' => {:name => 'SUNWcs', :ensure => '0.5.11,5.11-0.151.0.1:20101105T001108Z', :status => 'installed', :provider => :pkg, :publisher => 'solaris'}, }.each do |k, v| it "[#{k}] should correctly parse" do described_class.parse_line(k).should == v end end end context ":latest" do it "should work correctly for ensure latest on solaris 11 (UFOXI) when there are no further packages to install" do - described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.installed')) - provider.latest.should == "1.0.6-0.175.0.0.0.2.537" + described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.installed')) + provider.latest.should == '1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z' end it "should work correctly for ensure latest on solaris 11 in the presence of a certificate expiration warning" do - described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.certificate_warning')) + described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.certificate_warning')) provider.latest.should == "1.0.6-0.175.0.0.0.2.537" end it "should work correctly for ensure latest on solaris 11(known UFOXI)" do Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '-n', 'dummy'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 0 - described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.known')) - provider.latest.should == "1.0.6-0.175.0.0.0.2.537" + described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.known')) + provider.latest.should == '1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z' end it "should work correctly for ensure latest on solaris 11 (IFO)" do - described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.ifo.installed')) - provider.latest.should == "1.0.6-0.175.0.0.0.2.537" + described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.ifo.installed')) + provider.latest.should == '1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z' end it "should work correctly for ensure latest on solaris 11(known IFO)" do Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '-n', 'dummy'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 0 - described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.ifo.known')) - provider.latest.should == "1.0.6-0.175.0.0.0.2.537" + described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.ifo.known')) + provider.latest.should == '1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z' end it "issues a warning when the certificate has expired" do warning = "Certificate '/var/pkg/ssl/871b4ed0ade09926e6adf95f86bf17535f987684' for publisher 'solarisstudio', needed to access 'https://pkg.oracle.com/solarisstudio/release/', will expire in '29' days." Puppet.expects(:warning).with("pkg warning: #{warning}") - described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.certificate_warning')) + described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.certificate_warning')) provider.latest end it "doesn't issue a warning when the certificate hasn't expired" do Puppet.expects(:warning).with(/pkg warning/).never - described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.installed')) + described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.installed')) provider.latest end end context ":instances" do - it "should correctly parse lines with preferred publisher" do - described_class.expects(:pkg).with(:list,'-H').returns File.read(my_fixture('simple')) - instances = described_class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } - instances.size.should == 2 - instances[0].should == {:name => 'SUNWdummy', :ensure => "2.5.5-0.111"} - instances[1].should == {:name => 'dummy2', :ensure =>"9.3.6.1-0.111"} - end - - it "should correctly parse lines with non preferred publisher" do - described_class.expects(:pkg).with(:list,'-H').returns File.read(my_fixture('publisher')) - instances = described_class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } - instances.size.should == 2 - instances[0].should == {:name => 'SUNWdummy', :ensure => "8.8-0.111"} - instances[1].should == {:name => 'service/network/dummy', :ensure => "0.5.11-0.151.0.1"} - end - it "should correctly parse lines on solaris 11" do - described_class.expects(:pkg).with(:list, '-H').returns File.read(my_fixture('solaris11')) + described_class.expects(:pkg).with(:list, '-Hv').returns File.read(my_fixture('solaris11')) described_class.expects(:warning).never instances = described_class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure) }} instances.size.should == 2 - instances[0].should == {:name => 'dummy/dummy', :ensure => "3.0-0.175.0.0.0.2.537"} - instances[1].should == {:name => 'dummy/dummy2', :ensure => "1.8.1.2-0.175.0.0.0.2.537"} + instances[0].should == {:name => 'dummy/dummy', :ensure => '3.0,5.11-0.175.0.0.0.2.537:20131230T130000Z'} + instances[1].should == {:name => 'dummy/dummy2', :ensure => '1.8.1.2-0.175.0.0.0.2.537:20131230T130000Z'} end it "should fail on incorrect lines" do fake_output = File.read(my_fixture('incomplete')) - described_class.expects(:pkg).with(:list,'-H').returns fake_output + described_class.expects(:pkg).with(:list,'-Hv').returns fake_output expect { described_class.instances }.to raise_error(ArgumentError, /Unknown line format pkg/) end it "should fail on unknown package status" do - described_class.expects(:pkg).with(:list,'-H').returns File.read(my_fixture('unknown_status')) + described_class.expects(:pkg).with(:list,'-Hv').returns File.read(my_fixture('unknown_status')) expect { described_class.instances }.to raise_error(ArgumentError, /Unknown format pkg/) end end context ":query" do context "on solaris 10" do it "should find the package" do - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-H', 'dummy'], {:failonfail => false, :combine => true}).returns File.read(my_fixture('dummy_solaris10')) + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}).returns File.read(my_fixture('dummy_solaris10')) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.query.should == { - :name => 'dummy', - :ensure => "2.5.5-0.111", - :status => "installed", - :provider => :pkg, + :name => 'dummy', + :ensure => '2.5.5,5.10-0.111:20131230T130000Z', + :publisher => 'solaris', + :status => 'installed', + :provider => :pkg, } end it "should return :absent when the package is not found" do - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-H', 'dummy'], {:failonfail => false, :combine => true}).returns '' + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 1 provider.query.should == {:ensure => :absent, :name => "dummy"} end end context "on solaris 11" do it "should find the package" do $CHILD_STATUS.stubs(:exitstatus).returns 0 - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-H', 'dummy'], {:failonfail => false, :combine => true}).returns File.read(my_fixture('dummy_solaris11.installed')) + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}).returns File.read(my_fixture('dummy_solaris11.installed')) provider.query.should == { - :name => 'dummy', - :status => 'installed', - :ensure => "1.0.6-0.175.0.0.0.2.537", - :provider => :pkg + :name => 'dummy', + :status => 'installed', + :ensure => '1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z', + :publisher => 'solaris', + :provider => :pkg, } end it "should return :absent when the package is not found" do - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-H', 'dummy'], {:failonfail => false, :combine => true}).returns '' + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 1 provider.query.should == {:ensure => :absent, :name => "dummy"} end end it "should return fail when the packageline cannot be parsed" do - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-H', 'dummy'], {:failonfail => false, :combine => true}).returns(File.read(my_fixture('incomplete'))) + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}).returns(File.read(my_fixture('incomplete'))) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect { provider.query }.to raise_error(ArgumentError, /Unknown line format/) end end context ":install" do it "should accept all licenses" do + provider.expects(:query).with().returns({:ensure => :absent}) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '--accept', 'dummy'], {:failonfail => false, :combine => true}).returns '' Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.install end it "should install specific version(1)" do # Should install also check if the version installed is the same version we are asked to install? or should we rely on puppet for that? - resource[:ensure] = '0.0.7' + resource[:ensure] = '0.0.7,5.11-0.151006:20131230T130000Z' $CHILD_STATUS.stubs(:exitstatus).returns 0 Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-H', 'dummy'], {:failonfail => false, :combine => true}).returns 'dummy 0.0.6 installed -----' - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '--accept', 'dummy@0.0.7'], {:failonfail => false, :combine => true}).returns '' + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}).returns 'pkg://foo/dummy@0.0.6,5.11-0.151006:20131230T130000Z installed -----' + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '--accept', 'dummy@0.0.7,5.11-0.151006:20131230T130000Z'], {:failonfail => false, :combine => true}).returns '' provider.install end it "should install specific version(2)" do resource[:ensure] = '0.0.8' Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-H', 'dummy'], {:failonfail => false, :combine => true}).returns 'dummy 0.0.7 installed -----' - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '--accept', 'dummy@0.0.8'], {:failonfail => false, :combine => true}).returns '' + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}).returns 'pkg://foo/dummy@0.0.7,5.11-0.151006:20131230T130000Z installed -----' + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '--accept', 'dummy@0.0.8'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.install end - it "should install specific version(3)" do + it "should downgrade to specific version" do resource[:ensure] = '0.0.7' - provider.expects(:query).with().returns({:ensure => '0.0.8'}) - provider.expects(:uninstall).with() + provider.expects(:query).with().returns({:ensure => '0.0.8,5.11-0.151106:20131230T130000Z'}) $CHILD_STATUS.stubs(:exitstatus).returns 0 Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) - Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '--accept', 'dummy@0.0.7'], {:failonfail => false, :combine => true}).returns '' + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '--accept', 'dummy@0.0.7'], {:failonfail => false, :combine => true}).returns '' provider.install end it "should install any if version is not specified" do resource[:ensure] = :present + provider.expects(:query).with().returns({:ensure => :absent}) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '--accept', 'dummy'], {:failonfail => false, :combine => true}).returns '' Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.install end it "should install if no version was previously installed, and a specific version was requested" do resource[:ensure] = '0.0.7' provider.expects(:query).with().returns({:ensure => :absent}) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '--accept', 'dummy@0.0.7'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.install end + + it "installs the latest matching version when given implicit version, and none are installed" do + resource[:ensure] = '1.0-0.151006' + is = :absent + provider.expects(:query).with().returns({:ensure => is}) + described_class.expects(:pkg).with(:list, '-Hvfa', 'dummy@1.0-0.151006').returns File.read(my_fixture('dummy_implicit_version')) + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '-n', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) + provider.expects(:unhold).with() + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '--accept', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) + $CHILD_STATUS.stubs(:exitstatus).returns 0 + provider.insync?(is) + provider.install + end + + it "updates to the latest matching version when given implicit version" do + resource[:ensure] = '1.0-0.151006' + is = '1.0,5.11-0.151006:20140219T191204Z' + provider.expects(:query).with().returns({:ensure => is}) + described_class.expects(:pkg).with(:list, '-Hvfa', 'dummy@1.0-0.151006').returns File.read(my_fixture('dummy_implicit_version')) + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '-n', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) + provider.expects(:unhold).with() + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '--accept', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) + $CHILD_STATUS.stubs(:exitstatus).returns 0 + provider.insync?(is) + provider.install + end + + it "issues a warning when an implicit version number is used, and in sync" do + resource[:ensure] = '1.0-0.151006' + is = '1.0,5.11-0.151006:20140220T084443Z' + provider.expects(:warning).with("Implicit version 1.0-0.151006 has 3 possible matches") + described_class.expects(:pkg).with(:list, '-Hvfa', 'dummy@1.0-0.151006').returns File.read(my_fixture('dummy_implicit_version')) + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '-n', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) + $CHILD_STATUS.stubs(:exitstatus).returns 4 + provider.insync?(is) + end + + it "issues a warning when choosing a version number for an implicit match" do + resource[:ensure] = '1.0-0.151006' + is = :absent + provider.expects(:warning).with("Implicit version 1.0-0.151006 has 3 possible matches") + provider.expects(:warning).with("Selecting version '1.0,5.11-0.151006:20140220T084443Z' for implicit '1.0-0.151006'") + described_class.expects(:pkg).with(:list, '-Hvfa', 'dummy@1.0-0.151006').returns File.read(my_fixture('dummy_implicit_version')) + Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '-n', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) + $CHILD_STATUS.stubs(:exitstatus).returns 0 + provider.insync?(is) + end end context ":update" do it "should not raise error if not necessary" do provider.expects(:install).with(true).returns({:exit => 0}) provider.update end it "should not raise error if not necessary (2)" do provider.expects(:install).with(true).returns({:exit => 4}) provider.update end it "should raise error if necessary" do provider.expects(:install).with(true).returns({:exit => 1}) expect { provider.update }.to raise_error(Puppet::Error, /Unable to update/) end end context ":uninstall" do it "should support current pkg version" do described_class.expects(:pkg).with(:version).returns('630e1ffc7a19') described_class.expects(:pkg).with([:uninstall, resource[:name]]) provider.uninstall end it "should support original pkg commands" do described_class.expects(:pkg).with(:version).returns('052adf36c3f4') described_class.expects(:pkg).with([:uninstall, '-r', resource[:name]]) provider.uninstall end end end end