# Packaging using Peter Bonivart's pkgutil program.
Puppet::Type.type(:package).provide :pkgutil, :parent => :sun, :source => :sun do
  desc "Package management using Peter Bonivart's ``pkgutil`` command on Solaris."

  pkgutil_bin = "pkgutil"
  if FileTest.executable?("/opt/csw/bin/pkgutil")
    pkgutil_bin = "/opt/csw/bin/pkgutil"
  end

  confine :operatingsystem => :solaris

  commands :pkguti => pkgutil_bin

  def self.healthcheck()
    unless FileTest.exists?("/var/opt/csw/pkgutil/admin")
      Puppet.notice "It is highly recommended you create '/var/opt/csw/pkgutil/admin'."
      Puppet.notice "See /var/opt/csw/pkgutil"
    end

    correct_wgetopts = false
    [ "/opt/csw/etc/pkgutil.conf", "/etc/opt/csw/pkgutil.conf" ].each do |confpath|
      File.open(confpath) do |conf|
        conf.each {|line| correct_wgetopts = true if line =~ /^\s*wgetopts\s*=.*(-nv|-q|--no-verbose|--quiet)/ }
      end
    end
    if ! correct_wgetopts
      Puppet.notice "It is highly recommended that you set 'wgetopts=-nv' in your pkgutil.conf."
    end
  end

  def self.instances(hash = {})
    healthcheck

    # Use the available pkg list (-a) to work out aliases
    aliases = {}
    availlist.each do |pkg|
      aliases[pkg[:name]] = pkg[:alias]
    end

    # The -c pkglist lists installed packages
    pkginsts = []
    pkglist(hash).each do |pkg|
      pkg.delete(:avail)
      pkginsts << new(pkg)

      # Create a second instance with the alias if it's different
      pkgalias = aliases[pkg[:name]]
      if pkgalias and pkg[:name] != pkgalias
        apkg = pkg.dup
        apkg[:name] = pkgalias
        pkginsts << new(apkg)
      end
    end

    pkginsts
  end

  # Turns a pkgutil -a listing into hashes with the common alias, full
  # package name and available version
  def self.availlist
    output = pkguti ["-a"]

    list = output.split("\n").collect do |line|
      next if line =~ /^common\s+package/  # header of package list
      next if noise?(line)

      if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/
        { :alias => $1, :name => $2, :avail => $3 }
      else
        Puppet.warning "Cannot match %s" % line
      end
    end.reject { |h| h.nil? }
  end

  # Turn our pkgutil -c listing into a bunch of hashes.
  # Supports :justme => packagename, which uses the optimised --single arg
  def self.pkglist(hash)
    command = ["-c"]

    if hash[:justme]
      # The --single option speeds up the execution, because it queries
      # the package managament system for one package only.
      command << "--single"
      command << hash[:justme]
    end

    output = pkguti(command).split("\n")

    if output[-1] == "Not in catalog"
      Puppet.warning "Package not in pkgutil catalog: %s" % hash[:justme]
      return nil
    end

    list = output.collect do |line|
      next if line =~ /installed\s+catalog/  # header of package list
      next if noise?(line)

      pkgsplit(line)
    end.reject { |h| h.nil? }

    if hash[:justme]
      # Single queries may have been for an alias so return the name requested
      if list.any?
        list[-1][:name] = hash[:justme]
        return list[-1]
      end
    else
      list.reject! { |h| h[:ensure] == :absent }
      return list
    end
  end

  # Identify common types of pkgutil noise as it downloads catalogs etc
  def self.noise?(line)
    true if line =~ /^#/
    true if line =~ /^Checking integrity / # use_gpg
    true if line =~ /^gpg: /               # gpg verification
    true if line =~ /^=+> /                # catalog fetch
    true if line =~ /\d+:\d+:\d+ URL:/     # wget without -q
    false
  end

  # Split the different lines into hashes.
  def self.pkgsplit(line)
    if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/
      hash = {}
      hash[:name] = $1
      hash[:ensure] = if $2 == "notinst"
        :absent
      else
        $2
      end
      hash[:avail] = $3

      if hash[:avail] =~ /^SAME\s*$/
        hash[:avail] = hash[:ensure]
      end

      # Use the name method, so it works with subclasses.
      hash[:provider] = self.name

      return hash
    else
      Puppet.warning "Cannot match %s" % line
      return nil
    end
  end

  def install
    pkguti "-y", "-i", @resource[:name]
  end

  # Retrieve the version from the current package file.
  def latest
    hash = self.class.pkglist(:justme => @resource[:name])
    hash[:avail] if hash
  end

  def query
    if hash = self.class.pkglist(:justme => @resource[:name])
      hash
    else
      {:ensure => :absent}
    end
  end

  def update
    pkguti "-y", "-u", @resource[:name]
  end

  def uninstall
    pkguti "-y", "-r", @resource[:name]
  end
end
