#!/usr/bin/env ruby

require 'socket'
require 'optparse'
require 'fileutils'
require 'date'

STANDARD_BACKUP_FILES = ['config_files.tar.gz', 'pulp_data.tar*']
ONLINE_BACKUP_FILES = ['mongo_dump', 'candlepin.dump', 'foreman.dump', 'pg_globals.dump']
OFFLINE_BACKUP_FILES = ['mongo_data.tar.gz', 'pgsql_data.tar.gz']
FOREMAN_PROXY_CONTENT_BACKUP_FILES = ['mongo_data.tar.gz']
FOREMAN_PROXY_CONTENT_ONLINE_BACKUP_FILES = ['mongo_dump']
FOREMAN_PROXY_CONTENT = "capsule" # keep this variable easy to change for backporting

confirmed = false
@disable_system_checks = false
@foreman_proxy_content = !`rpm -qa | grep satellite-capsule`.empty?
@skip_register = false

@optparse = OptionParser.new do |opts|
  opts.banner = "Usage: katello-restore /path/to/dir [options]\n eg: $ katello-restore /tmp/backup/katello-backup-2016-09-30T00:00:00+00:00"

  opts.on("-y", "--assumeyes", "Answer yes for all questions") do
    confirmed = true
  end

  opts.on("-d","--disable-system-checks","runs the installer with --disable-system-checks") do
    @disable_system_checks = true
  end

  opts.parse!

  if ARGV.length == 0
    opts.abort("**** ERROR: Please specify the backup directory to restore ****")
  elsif ARGV.length != 1
    puts opts
    exit(-1)
  end

  @dir = ARGV.pop.dup
  unless File.directory?(@dir)
    opts.abort("Backup directory does not exist: #{@dir}")
  end
end

def run_cmd(command, exit_codes=[0])
  result = `#{command}`
  unless exit_codes.include?($?.exitstatus)
    STDERR.puts "Failed '#{command}'"
    exit($?.exitstatus)
  end
  result
end

def set_file_security
  puts "Setting file security"
  run_cmd("restorecon -Rnv /")
  puts "Done.\n"
end

def reset_katello
  puts "Resetting Katello"
  run_cmd("tar --selinux --overwrite --listed-incremental=/dev/null -xzf config_files.tar.gz -C /")
  installer = "yes | satellite-installer -v --reset"
  if @foreman_proxy_content
    installer << " --scenario #{FOREMAN_PROXY_CONTENT} --foreman-proxy-register-in-foreman false"
  else
    installer << " --scenario satellite"
  end
  installer << " --disable-system-checks" if @disable_system_checks
  puts installer
  installer_output = run_cmd(installer, [0,6])
  puts installer_output

  puts "Done.\n"
end

def restore_psql_dumps
  puts "Restoring postgres dump files"
  run_cmd("katello-service start --only postgresql")
  run_cmd("runuser - postgres -c 'dropdb foreman'")
  run_cmd("runuser - postgres -c 'dropdb candlepin'")
  run_cmd("runuser - postgres -c 'psql -f #{File.join(@dir, "pg_globals.dump")} postgres 2>/dev/null'")
  run_cmd("runuser - postgres -c 'pg_restore -C -d postgres #{File.join(@dir, "foreman.dump")}'")
  run_cmd("runuser - postgres -c 'pg_restore -C -d postgres #{File.join(@dir, "candlepin.dump")}'")
  run_cmd("katello-service stop --only postgresql")
  puts "Done."
end

def migrate_pulp
  puts "Migrating pulp databases"
  necessary_services = "mongod,qpidd"
  pulp_services = "pulp,celerybeat,pulp_workers,pulp_resource_manager"
  run_cmd("katello-service start --only #{necessary_services}")
  run_cmd("katello-service stop --only #{pulp_services}")
  run_cmd("sudo -u apache pulp-manage-db")
  puts "Done."
end

def restore_mongo_dump
  puts "Restoring mongo dump"
  run_cmd("katello-service start --only mongod")
  run_cmd("echo 'db.dropDatabase();' | mongo pulp_database")
  run_cmd("mongorestore --host localhost mongo_dump/pulp_database/")
  run_cmd("katello-service stop --only mongod")
  puts "Done."
end

def valid_logical_backup
  base_files_present =  @mongo_dump_exists && @mongo_data_exists
  if @foreman_proxy_content
    base_files_present
  else
    base_files_present && @pgsql_data_exists && @candlepin_dump_exists && @foreman_dump_exists
  end
end

def valid_online_backup
  @candlepin_dump_exists && @foreman_dump_exists && @mongo_dump_exists && @pg_globals_exist &&
  !(@mongo_data_exists || @pgsql_data_exists)
end

def valid_fpc_online_backup
  @mongo_dump_exists &&
  !(@mongo_data_exists || @pgsql_data_exists || @candlepin_dump_exists || @foreman_dump_exists)
end

def valid_standard_backup
  @mongo_data_exists && @pgsql_data_exists &&
  !(@candlepin_dump_exists || @foreman_dump_exists || @mongo_dump_exists)
end

def valid_fpc_standard_backup
  @mongo_data_exists &&
  !(@pgsql_data_exists || @candlepin_dump_exists || @foreman_dump_exists || @mongo_dump_exists)
end

def restore_databases
  puts "Logical backup detected, using the standard backup files to restore" if valid_logical_backup
  if @pulp_data_exists
    puts "Restoring Pulp data"
    run_cmd("tar --selinux --overwrite --listed-incremental=/dev/null -xf pulp_data.tar -C /")
  end
  if @mongo_data_exists
    run_cmd("tar --selinux --overwrite --listed-incremental=/dev/null -xzf mongo_data.tar.gz -C /")
  end
  if @pgsql_data_exists
    run_cmd("tar --selinux --overwrite --listed-incremental=/dev/null -xzf pgsql_data.tar.gz -C /")
  end
  if !@mongo_data_exists && !@pgsql_data_exists && !valid_logical_backup
    if @foreman_dump_exists && @candlepin_dump_exists
      restore_psql_dumps
    end
    if @mongo_dump_exists
      restore_mongo_dump
    end
  end
  migrate_pulp
  puts "Done.\n"
end

def restore
  FileUtils.chown(nil, 'postgres', @dir) unless @foreman_proxy_content
  Dir.chdir(@dir)

  set_file_security
  reset_katello

  puts "Stopping Katello services"
  run_cmd("katello-service stop")
  puts "Done.\n"

  restore_databases

  puts "Ensuring all Katello processes are started"
  run_cmd("katello-service start")
  puts "Done.\n"
end

def display_backup_options
  puts "---- The given directory does not contain the required files or has too many files"
  puts "---- All backup directories contain: #{STANDARD_BACKUP_FILES.join(", ")}"
  if @foreman_proxy_content
    puts "---- A #{FOREMAN_PROXY_CONTENT.gsub(/-/, " ")} backup directory contains: #{FOREMAN_PROXY_CONTENT_BACKUP_FILES.join(", ")}"
    puts "---- A #{FOREMAN_PROXY_CONTENT.gsub(/-/, " ")} backup directory contains: #{FOREMAN_PROXY_CONTENT_ONLINE_BACKUP_FILES.join(", ")}"
  else
    puts "---- An online backup directory contains: #{ONLINE_BACKUP_FILES.join(", ")}"
    puts "---- An offline backup directory contains: #{OFFLINE_BACKUP_FILES.join(", ")}"
  end
  puts "---- A logical backup directory contains: #{ONLINE_BACKUP_FILES.join(", ")}, #{OFFLINE_BACKUP_FILES.join(", ")}"
  puts "---- *pulp_data.tar is optional"
  puts "---- Please choose a valid backup directory"
  puts @optparse
  exit(-1)
end

def backup_valid?
  @mongo_data_exists = File.exist?(File.join(@dir, 'mongo_data.tar.gz'))
  @pgsql_data_exists = File.exist?(File.join(@dir, 'pgsql_data.tar.gz'))
  @pulp_data_exists = File.exist?(File.join(@dir, 'pulp_data.tar'))
  @foreman_dump_exists = File.exist?(File.join(@dir, 'foreman.dump'))
  @candlepin_dump_exists = File.exist?(File.join(@dir, 'candlepin.dump'))
  @mongo_dump_exists = File.directory?(File.join(@dir, 'mongo_dump'))
  @config_exists = File.exist?(File.join(@dir, 'config_files.tar.gz'))
  @pg_globals_exist = File.exist?(File.join(@dir, 'pg_globals.dump'))

  unless @config_exists
    puts "Cannot find the required config_files.tar.gz file in #{@dir}"
    exit(-1)
  end

  return true if valid_logical_backup

  if @foreman_proxy_content
    unless valid_fpc_standard_backup || valid_fpc_online_backup
      display_backup_options
    end
  else
    unless valid_standard_backup || valid_online_backup
      display_backup_options
    end
  end
  true
end

def confirm
  puts "WARNING: This script will drop and restore your database."
  puts "Your existing installation will be replaced with the backup database."
  puts "Once this operation is complete there is no going back.\n"
  print "Are you sure(Y/N)? "
  response = gets.chomp
  if /[Y]/i.match(response)
    puts "Starting restore from #{@dir}: #{Time.now}"
    restore
    puts "Done with restore: #{Time.now}"
  else
    puts "**** cancelled ****"
  end
end

if backup_valid?
  confirmed ? restore : confirm
else
  display_backup_options
end
