#!/usr/bin/ruby
#
# Check for pending security updates, via `unattended-upgrades`.
#
# When `unattended-upgrades` is not available `apt_legacy_check` will be
# used instead to handle the job.
#
#

require 'yaml'


#
# Allow access to our common-code.
#
$LOAD_PATH << '/usr/share/bytemark'
$LOAD_PATH << '../lib/bytemark' if ENV['TEST'] && ENV['TEST_PREFIX']

require 'healthcheck/command_output'


#
# Test to see if we have pending package upgrades.
#
# We only alert upon security upgrades.
#
class AptUpgrade

  #
  #  Is this a debian system?
  #
  def is_debian?
    File.exist?("/usr/bin/apt-get")  &&
      File.directory?("/var/lib/apt/lists")
  end


  #
  #  Return a hash of upgradable Debian packages.
  #
  #  The key contains the package-name, and the value contains the
  # origin of the upgrade.
  #
  def security_upgrades
    ret = Hash.new

    out = Bytemark::Healthcheck::CommandWrapper.run_command( "unattended-upgrade --dry-run -d" )
    out.split( "\n" ).each do |line|

      #
      # The lines will look something like:
      #
      #   Checking: libgtk-3-0 (["<Origin component:'main' archive:'oldstable' origin:'Debian' label:'Debian' site:'httpredir.debian.org' isTrusted:True>"])
      #   Checking: libgtk-3-bin (["<Origin component:'main' archive:'oldstable' origin:'Debian' label:'Debian' site:'httpredir.debian.org' isTrusted:True>"])
      #   Checking: libgtk-3-common (["<Origin component:'main' archive:'oldstable' origin:'Debian' label:'Debian' site:'httpredir.debian.org' isTrusted:True>"])
      #
      #  So we pick out the second token, as the package, and the section
      # inside the "(" & ")" as the origin/details.
      #
      if ( line =~ /^Checking: ([^ ]+) \(([^\)]+)\)/ )
        package = $1.dup
        origin  = $2.dup

        #
        # We only care about security updates
        #
        if ( origin =~ /security/i )
          ret[package]=origin
        end
      end
    end

    held = Hash.new

    # Make a hash of all held packages
    `dpkg --get-selections`.split( /[\r\n]/ ).each do |line|
      name,state = line.split()
      if ( state =~ /hold/i )
        verbose( "Package is held: #{name}" )
        held[name] = 1
      end
    end

    # We have a hash of packages.  Filter out those which are "held".
    filtered = Array.new

    ret.each_pair do |pkg,origin|
      if ( held[pkg] )
        verbose( "Ignoring upgradable pacakge #{pkg} because it is held" )
      else
        filtered.push( pkg )
      end
    end

    # Return the filtered array.
    filtered
  end

end



if __FILE__ ==  $PROGRAM_NAME

  def verbose(str)
    STDERR.puts(str)
  end

  to_raise = []

  #
  # See how many pending upgrades are present.
  #
  helper   = AptUpgrade.new()
  upgrades = nil

  #
  # If unattend-upgrades is not installed then don't look for updates
  # UNLESS we're running the test-suite.
  #
  # This covers the case of running tests on systems which don't have
  # unattended-upgrades installed - which we'd expect to still work.
  #
  if ( ( ENV['TEST'] && ENV['TEST_PREFIX'] ) ||
       ( File.exists?( "/usr/bin/unattended-upgrades" ) ) )
    upgrades = helper.is_debian? ? helper.security_upgrades : nil
  else
    verbose( "Disabling test - /usr/bin/unattended-upgrades is not present")
  end

  #
  # If we got upgrades, which are non-empty.
  #
  if ( ! upgrades.nil? && !upgrades.empty? )
    h = {}
    h[:id] = "apt-get-upgrade-low"
    h[:summary] = "There are #{upgrades.size} pending security-upgrades available."
    h[:detail] = "<p>The following packages are pending security-upgrades:</p><pre>#{upgrades.join( "\n" )}</pre>"

    to_raise.push(h)
  end

  #
  #  Show the output.
  #
  puts YAML.dump(to_raise)
  exit(0)
end
