#!/usr/bin/ruby

require 'shellwords'
require 'logger'
require 'optparse'
require 'tempfile'
require 'fileutils'

class RepoSignatureChecker
  def initialize
    @repo_dir = '/srv/www/htdocs/repo'
    @keyring = Tempfile.new('repo-check-keyring')
    @keyring.close

    parse_options
    @logger = Logger.new(@opt_nagios ? IO::NULL : STDOUT)
  end

  def parse_options
    OptionParser.new do |opts|
      opts.banner = "Verifies repository metadata GPG signatures.\nUsage: #{File.basename($0)} [options]"

      opts.on("-n", "--nagios", "Terse output for Nagios") { @opt_nagios = true }
      opts.on("-d", "--dir [REPO_DIR]", "Path to repo directory") { |param| @repo_dir = param }
    end.parse!
  end

  def check_repo(dir)
    cmd = "gpg --no-default-keyring --keyring #{@keyring.path} --import #{dir}/repomd.xml.key 2>&1"
    out = `#{cmd}`

    if $?.exitstatus != 0
      @logger.warn cmd
      @logger.warn out
      return false
    end

    cmd = "gpg --no-default-keyring --keyring #{@keyring.path} --verify #{dir}/repomd.xml.asc #{dir}/repomd.xml 2>&1"
    out = `#{cmd}`

    if $?.exitstatus != 0
      @logger.warn cmd
      @logger.warn out
      return false
    end

    true
  end

  def update_prometheus_data(valid, invalid, invalid_paths)
    node_exporter_path = '/var/lib/node_exporter/textfile_collector/check_repo_signatures.prom'
    unless Dir.exist? File.dirname(node_exporter_path)
      begin
        FileUtils.mkdir_p File.dirname(node_exporter_path)
      rescue Errno::EACCES => e
        puts "Failed to create #{node_exporter_path}: #{e.message}"
        return
      end
    end

    script = 'check_repo_signatures'
    content = "#{script} {status=\"valid_repos\"} #{valid}\n" \
              "#{script} {status=\"invalid_repos\",repos=\"#{invalid_paths}\"} #{invalid}\n"
    File.open(node_exporter_path, 'w') { |f| f.write(content) }
  end

  def run
    repodata_dirs = `find #{@repo_dir} -type d -name repodata`
    if repodata_dirs.empty?
      puts "No repositories found in #{@repo_dir}"
      exit 2
    end

    valid_repos = 0
    invalid_repos = 0
    invalid_repos_paths = []

    repodata_dirs.each_line do |line|
      dir = Shellwords.escape(line.strip)
      result = check_repo(dir)
      if result
        valid_repos += 1
      else
        invalid_repos += 1
        invalid_repos_paths << dir
      end
    end

    update_prometheus_data(valid_repos, invalid_repos, invalid_repos_paths.join(', '))

    puts "Valid repos=#{valid_repos}, invalid repos=#{invalid_repos}"
    if invalid_repos > 0
      exit 2
    else
      exit 0
    end
  ensure
    gpg_backup_file = @keyring.path + "~"
    File.unlink(gpg_backup_file) if File.exist?(gpg_backup_file)
    @keyring.unlink
  end
end

RepoSignatureChecker.new.run
