diff --git a/katello/backup.rb b/katello/backup.rb new file mode 100644 index 00000000..5e3dde1f --- /dev/null +++ b/katello/backup.rb @@ -0,0 +1,428 @@ +require 'optparse' +require 'fileutils' +require 'date' +require 'yaml' +require 'find' +require 'json' +require 'highline/import' +require_relative "helper.rb" + +module KatelloUtilities + class Backup + include ::KatelloUtilities::Helper + + def initialize(foreman_proxy_content, foreman_proxy, program, accepted_scenarios=nil) + @excluded="--exclude goferd,foreman-proxy,squid,smart_proxy_dynflow_core,qdrouterd,qpidd" + @databases = ['pulp', 'pgsql', 'mongodb'] + + @options = {} + @dir = nil + @databases = @databases.dup + @accepted_scenarios = accepted_scenarios + + # keep as variables for easy backporting + @foreman_proxy_content = foreman_proxy_content + @foreman_proxy = foreman_proxy + @program = program + + setup_opt_parser + end + + def cleanup(exitstatus=-1) + puts "Cleaning up backup folder and starting any stopped services... " + FileUtils.cd("/") + FileUtils.rm_rf @dir unless @options[:no_subdir] + `katello-service start #{@excluded}` unless @options[:online] + puts "Done." + exit(exitstatus) + end + + def specified_features + @options[:features] || [] + end + + def feature_included?(feature) + specified_features.include?(feature) || specified_features.include?("all") + end + + def configure_configs + base_configs = [ + "/etc/foreman-proxy", + "/etc/httpd", + "/etc/foreman-installer", + "/etc/pki/katello", + "/etc/pki/katello-certs-tools", + "/etc/pki/pulp", + "/etc/pulp", + "/etc/puppet", + "/etc/qpid", + "/etc/qpid-dispatch", + "/root/ssl-build", + "/var/www/html/pub", + "/etc/squid", + "/etc/puppetlabs", + '/opt/puppetlabs/puppet/cache/foreman_cache_data', + '/opt/puppetlabs/puppet/ssl/', + '/var/lib/puppet/foreman_cache_data', + '/var/lib/puppet/ssl' + ] + + katello_configs = [ + "/etc/candlepin", + "/etc/foreman", + "/etc/hammer", + "/etc/sysconfig/tomcat*", + "/etc/tomcat*", + "/var/lib/candlepin", + ] + + if @is_foreman_proxy_content + fpc_certs_tar = run_cmd("cat /etc/foreman-installer/scenarios.d/#{@foreman_proxy_content}-answers.yaml | grep certs_tar | awk '{print $2}'").chomp + unless File.exist?(fpc_certs_tar) + puts "#{@foreman_proxy} certs tar file is not present on the system in path #{fpc_certs_tar} please move the file back to that location or generate a new one on the main server." + cleanup + end + backup_configs = base_configs.push(fpc_certs_tar) + else + backup_configs = base_configs + katello_configs + end + + feature_configs = [] + feature_configs.push("/var/lib/tftpboot") if feature_included?("tftp") + feature_configs += ["/var/named/", "/etc/named*"] if feature_included?("dns") + feature_configs += ["/var/lib/dhcpd", "/etc/dhcp"] if feature_included?("dhcp") + feature_configs.push("/usr/share/xml/scap") if feature_included?("openscap") + + backup_configs + feature_configs + end + + def confirm + unless agree("WARNING: This script will stop your services. Do you want to proceed(y/n)? ") + puts "**** cancelled ****" + FileUtils.rm_rf @dir + exit(-1) + end + end + + def warning + unless agree("*** WARNING: The online backup flag is intended for making a copy of the data\n" \ + "*** for debugging purposes only. The backup routine can not ensure 100% consistency while the\n" \ + "*** backup is taking place as there is a chance there may be data mismatch between\n" \ + "*** Mongo and Postgres databases while the services are live. If you wish to utilize the --online-backup\n" \ + "*** flag for production use you need to ensure that there are no modifications occurring during\n" \ + "*** your backup run.\n\nDo you want to proceed(y/n)? ") + puts "**** cancelled ****" + FileUtils.rm_rf @dir + exit(-1) + end + end + + def create_directories(directory) + @dir = File.join directory, "#{@program}-backup-" + self.timestamp unless @options[:no_subdir] + puts "Creating backup folder #{@dir}" + FileUtils.mkdir_p @dir + unless @options[:no_subdir] + FileUtils.chown_R nil, 'postgres', @dir if @databases.include? "pgsql" + FileUtils.chmod_R 0770, @dir + end + end + + def snapshot_backup + @mountdir = @options[:snapshot_mount] || "/var/snap" + @snapsize = @options[:snapshot_size] || "2G" + FileUtils.mkdir_p @mountdir + confirm unless @options[:confirm] + run_cmd("katello-service stop #{@excluded}") + create_and_mount_snapshots + run_cmd("katello-service start #{@excluded}") + backup_and_destroy_snapshots + end + + def create_and_mount_snapshots + @databases.each do |database| + puts "Creating #{database} snapshot" + lv_info = get_lv_info(database) + run_cmd("lvcreate -n#{database}-snap -L#{@snapsize} -s #{lv_info[0]}") + + mount_location = File.join(@mountdir, database) + FileUtils.mkdir_p mount_location + puts "Mounting #{database} snapshot on #{mount_location}" + options = lv_info[1] == 'xfs' ? "-onouuid,ro" : "-oro" + run_cmd("mount #{get_snapshot_location(database)} #{mount_location} #{options}") + end + end + + def get_snapshot_location(database) + run_cmd("lvs --noheadings -o lv_path -S lv_name=#{database}-snap").strip + end + + def get_lv_info(database) + target = database + target = File.join(database, 'data') if target == 'pgsql' + run_cmd("findmnt -n --target #{File.join('/var/lib', target)} -o SOURCE,FSTYPE").split + end + + def get_base_directory(database) + target = "" + case database + when 'pulp' + target = '0005_puppet_module_name_change.txt' + when 'pgsql' + target = 'postgresql.conf' + when 'mongodb' + target = 'mongod.lock' + end + + result = nil + Find.find(File.join(@mountdir, database)) do |path| + result = File.dirname(path) if File.basename(path) == target + end + result + end + + def backup_and_destroy_snapshots + @databases.each do |database| + basedir = get_base_directory(database) + puts "Backing up #{database} from #{basedir}" + offline_backup(database, basedir) + + puts "Unmounting #{database} snapshot" + run_cmd("umount #{File.join(@mountdir, database)}") + + snapshot_location = get_snapshot_location(database) + puts "Removing snapshot #{snapshot_location}" + run_cmd("lvremove -f #{snapshot_location}") + end + end + + def online_backup(database) + case database + when 'pulp' + FileUtils.cd '/var/lib/pulp' do + puts "Backing up Pulp data... " + matching = false + until matching + checksum1 = run_cmd("find . -printf '%T@\n' | md5sum") + create_pulp_data_tar + checksum2 = run_cmd("find . -printf '%T@\n' | md5sum") + matching = (checksum1 == checksum2) + end + end + puts "Done." + when 'pgsql' + puts "Backing up postgres online schema... " + run_cmd("runuser - postgres -c 'pg_dumpall -g > #{File.join(@dir, 'pg_globals.dump')}'") + run_cmd("runuser - postgres -c 'pg_dump -Fc foreman > #{File.join(@dir, 'foreman.dump')}'") + run_cmd("runuser - postgres -c 'pg_dump -Fc candlepin > #{File.join(@dir, 'candlepin.dump')}'") + puts "Done." + when 'mongodb' + puts "Backing up mongo online schema... " + run_cmd("mongodump --host localhost --out #{File.join(@dir, 'mongo_dump')}") + puts "Done." + end + end + + def offline_backup(database, dir_path = nil) + case database + when 'pulp' + dir_path ||= '/var/lib/pulp' + FileUtils.cd dir_path do + puts "Backing up Pulp data... " + create_pulp_data_tar + puts "Done." + end + when 'mongodb' + dir_path ||= '/var/lib/mongodb' + FileUtils.cd dir_path do + puts "Backing up mongo db... " + run_cmd("tar --selinux --create --file=#{File.join(@dir, 'mongo_data.tar')} --listed-incremental=#{File.join(@dir, '.mongo.snar')} --exclude=mongod.lock --transform 's,^,var/lib/mongodb/,S' -S *") + puts "Done." + end + when 'pgsql' + dir_path ||= '/var/lib/pgsql/data' + FileUtils.cd dir_path do + puts "Backing up postgres db..." + run_cmd("tar --selinux --create --file=#{File.join(@dir, 'pgsql_data.tar')} --listed-incremental=#{File.join(@dir, '.postgres.snar')} --transform 's,^,var/lib/pgsql/data/,S' -S *") + puts "Done." + end + end + end + + def compress_files + psql = spawn('gzip', 'pgsql_data.tar', '-f') if @databases.include? "pgsql" + mongo = spawn('gzip', 'mongo_data.tar', '-f') + Process.wait(psql) if @databases.include? "pgsql" + Process.wait(mongo) + end + + def plugin_list + if @is_foreman_proxy_content + JSON.parse(run_cmd("curl -k https://$(hostname):9090/features")) + else + plugins = [] + plugin_list = run_cmd("foreman-rake plugin:list | grep 'Foreman plugin: '", [0,1]).lines + plugin_list.each do |line| + plugin = line.split + plugins << "#{plugin[2].chop}-#{plugin[3].chop}" + end + plugins + end + end + + def generate_metadata + puts "Generating metadata ... " + os_version = run_cmd("cat /etc/redhat-release").chomp + plugins = plugin_list + rpms = run_cmd("rpm -qa").split("\n") + system_facts = {os_version: os_version, plugin_list: plugins, rpms: rpms} + File.open('metadata.yml', 'w') do |metadata_file| + metadata_file.puts system_facts.to_yaml + end + puts "Done." + end + + def create_pulp_data_tar + run_cmd("tar --selinux --create --file=#{File.join(@dir, 'pulp_data.tar')} --exclude=var/lib/pulp/katello-export --listed-incremental=#{File.join(@dir, '.pulp.snar')} --transform 's,^,var/lib/pulp/,S' -S *") + end + + def backup_config_files + puts "Backing up config files... " + run_cmd("tar --selinux --create --gzip --file=#{File.join(@dir, 'config_files.tar.gz')} --listed-incremental=#{File.join(@dir, '.config.snar')} #{configure_configs.join(' ')} 2>/dev/null", [0,2]) + puts "Done." + end + + def validate_directory + return true unless @databases.include? "pgsql" + unless system("sudo -u postgres test -w #{@dir}") + puts "****cancelled****" + puts "Postgres user needs write access to the backup directory" + puts "Please select a directory, such as /tmp or /var/tmp which allows Postgres write access" + cleanup + end + end + + def setup_opt_parser + @optparse = OptionParser.new do |opts| + opts.banner = "Usage: #{@program}-backup /path/to/dir [options]\n eg: $ #{@program}-backup /tmp/#{@program}-backup" + + opts.on("--skip-pulp-content", "Create backup without Pulp content for debugging only") do |config_only| + @options[:config_only] = config_only + @databases.delete 'pulp' + end + + opts.on("--incremental PREVIOUS_BACKUP_DIR", String, "Backup changes since previous backup") do |dir_path| + opts.abort("Please specify the previous backup directory.") unless dir_path + if File.directory?(dir_path) + @options[:incremental] = dir_path + else + opts.abort("Previous backup directory does not exist: #{dir_path}") + end + end + + opts.on("--online-backup", "Keep services online during backup") do |online| + @options[:online] = online + end + + opts.on("--logical-db-backup", "Also dump full database schema during offline backup") do |logical| + @options[:logical_backup] = logical + end + + opts.on("--snapshot", "Use snapshots of the databases to create backup") do |snapshot| + @options[:snapshot] = snapshot + end + + opts.on("--snapshot-mount-dir SNAPSHOT_MOUNT_LOCATION", String, "Override default directory (/var/snap/) where the snapshots will be mounted") do |mount_dir| + @options[:snapshot_mount] = mount_dir + end + + opts.on("--snapshot-size SNAPSHOT_BLOCK_DEVICE_SIZE", String, "Override default block size (2G)") do |size| + @options[:snapshot_size] = size + end + + opts.on("--features FEATURES", Array, "#{@foreman_proxy.capitalize} features to include in the backup, please specify a list with commas. " \ + "Valid features are tftp, dns, dhcp, openscap, and all.") do |features| + @options[:features] = features.map { |f| f.to_s.downcase } + end + + opts.on("--preserve-directory", "Do not create a time-stamped subdirectory") do |no_subdir| + @options[:no_subdir] = no_subdir + end + + opts.on("-y", "--assumeyes", "Bypass interaction by answering yes") do |confirm| + @options[:confirm] = confirm + end + end + @optparse + end + + def parse_options + begin @optparse.parse! ARGV + if ARGV.length == 0 + @optparse.abort("**** ERROR: Please specify an export directory ****") + elsif ARGV.length != 1 + puts @optparse + exit(-1) + end + + @dir = ARGV[0].dup + rescue OptionParser::ParseError => e + puts e + puts @optparse + exit -1 + end + end + + def run + parse_options + + if @dir.nil? + puts "**** ERROR: Please specify an export directory ****" + puts @optparse + exit(-1) + end + + puts "Starting backup: #{Time.now}" + @is_foreman_proxy_content = !run_cmd("rpm -qa | grep foreman-proxy-content", [0,1]).empty? + @databases.delete 'pgsql' if @is_foreman_proxy_content + create_directories(@dir.dup) + validate_directory + + Dir.chdir(@dir) do + generate_metadata + if @options[:incremental] + FileUtils.cp Dir.glob(File.join(@options[:incremental], '.*.snar')), @dir + elsif @options[:no_subdir] + FileUtils.rm Dir.glob(File.join(@dir, '.*.snar')) + end + backup_config_files + + if @options[:logical_backup] + @databases.each do |database| + online_backup(database) + end + end + + if @options[:snapshot] + snapshot_backup + compress_files + elsif @options[:online] + warning unless @options[:confirm] + @databases.each do |database| + online_backup(database) + end + else + confirm unless @options[:confirm] + run_cmd("katello-service stop #{@excluded}") + @databases.each do |database| + offline_backup(database) + end + run_cmd("katello-service start #{@excluded}") + compress_files + end + end + + puts "Done with backup: #{Time.now}" + puts "**** BACKUP Complete, contents can be found in: #{@dir} ****" + end + end +end diff --git a/katello/helper.rb b/katello/helper.rb new file mode 100644 index 00000000..b044d5a7 --- /dev/null +++ b/katello/helper.rb @@ -0,0 +1,40 @@ +module KatelloUtilities + module Helper + def last_scenario + File.basename(File.readlink("/etc/foreman-installer/scenarios.d/last_scenario.yaml")).split(".")[0] + end + + def accepted_scenarios + @accepted_scenarios || ["katello", "foreman-proxy-content"] + end + + def error_message + "This utility can't run on a non-katello system." + end + + def run_cmd(command, exit_codes=[0], message=nil) + result = `#{command}` + unless exit_codes.include?($?.exitstatus) + STDOUT.puts result + STDOUT.puts message if message + failed_command = "Failed '#{command}' with exit code #{$?.exitstatus}" + if self.respond_to? :cleanup + STDOUT.puts failed_command + cleanup($?.exitstatus) + end + fail_with_message(failed_command) + end + result + end + + def timestamp + DateTime.now.strftime('%Y%m%d%H%M%S') + end + + def fail_with_message(message, opt_parser=nil) + STDOUT.puts message + puts opt_parser if opt_parser + exit(false) + end + end +end diff --git a/katello/hostname-change.rb b/katello/hostname-change.rb new file mode 100644 index 00000000..857d5f8d --- /dev/null +++ b/katello/hostname-change.rb @@ -0,0 +1,344 @@ +require "socket" +require "optparse" +require "rubygems" +require "yaml" +require "shellwords" +require "json" +require "uri" +require_relative "helper.rb" + +module KatelloUtilities + class HostnameChange + include ::KatelloUtilities::Helper + + def initialize(proxy, plural_proxy, proxy_hyphenated, program=nil, scenario=nil, accepted_scenarios=nil) + @default_program = self.get_default_program + @proxy = proxy + @plural_proxy = plural_proxy + @proxy_hyphenated = proxy_hyphenated + @accepted_scenarios = accepted_scenarios + + @last_scenario = self.last_scenario + @options = {} + @options[:program] = program || @default_program + @options[:scenario] = scenario || @last_scenario + @options[:system_check] = false + @foreman_proxy_content = @options[:scenario] == @proxy_hyphenated + + setup_opt_parser + end + + def disable_system_check_option? + katello_installer_rpm = `rpm -qa | grep katello-installer` + katello_installer_version = katello_installer_rpm[/(\d+\.)(\d+\.)(\d+)/] + Gem::Version.new(katello_installer_version) >= Gem::Version.new("3.2.0") + end + + def get_default_program + case @last_scenario + when "katello" + return "foreman" + when @proxy_hyphenated + return "foreman" + else + return @last_scenario + end + end + + def get_hostname + Socket.gethostname.chomp + end + + def yesno + begin + system("stty raw -echo") + str = STDIN.getc + ensure + system("stty -raw echo") + end + if str.chr.downcase == "y" + return true + elsif str.chr.downcase == "n" + return false + else + puts "Invalid Character. Try again: [y/n]" + self.yesno + end + end + + def check_for_certs_tar + STDOUT.puts "Checking for certs tarball" + if @options[:certs_tar] + if File.file?(@options[:certs_tar]) + true + else + self.fail_with_message("#{@options[:certs_tar]} does not exist! Please check the file path and try again") + end + else + self.fail_with_message("You must specify --certs-tar argument when on a #{@proxy}." \ + " These can be generated on the #{@default_program} server using " \ + "#{@proxy.downcase.gsub(" ", "-")}-certs-generate and copied to this machine.") + end + end + + def get_fpc_answers + register_in_foreman = false + certs_tar = @options[:certs_tar] + " --foreman-proxy-register-in-foreman #{register_in_foreman} --#{@proxy_hyphenated}-certs-tar #{certs_tar}" + end + + def precheck + unless @options[:username] && @options[:password] + self.fail_with_message("Username and/or Password options are missing!", @opt_parser) + end + + if ARGV[0] && ARGV.count >= 1 + @new_hostname = ARGV[0] + else + self.fail_with_message("Please specify a hostname.", @opt_parser) + end + + STDOUT.puts "\nChecking hostname validity" + # This regex is an approximation of a hostname, it will handle most invalid hostnames and typos. + # Taken from https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html + hostname_regex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/ + unless hostname_regex === @new_hostname + self.fail_with_message("#{@new_hostname} is not a valid fully qualified domain name, please use a valid FQDN and try again. " \ + "No changes have been made to your system."); + end + + unless @foreman_proxy_content + STDOUT.puts "\nChecking overall health of server" + self.run_cmd("hammer ping", [0], "There is a problem with the server, please check 'hammer ping'") + STDOUT.puts "\nChecking credentials" + self.hammer_cmd("capsule list", [0], "There is a problem with the credentials, please retry") + end + + if @options[:confirm] + response = true + else + STDOUT.print(self.warning_message) + response = self.yesno + end + + unless response + self.fail_with_message("Hostname change aborted, no changes have been made to your system") + end + end + + def successful_hostname_change_message + # the following multi-line string isn't indented because the indents are taken literally. + successful_message = %( + If you want to use custom certificates, re-run the #{@options[:program]}-installer with custom certificate options. + + You will have to install the new bootstrap rpm and reregister all clients and #{@plural_proxy} with subscription-manager + (update organization and environment arguments appropriately): + + yum remove -y katello-ca-consumer* + rpm -Uvh http://#{@new_hostname}/pub/katello-ca-consumer-latest.noarch.rpm + subscription-manager register --org="Default_Organization" --environment="Library" --force + + Then reattach subscriptions to the client(s) and run: + + subscription-manager refresh + yum repolist + + + On all #{@plural_proxy}, you will need to re-run the #{@options[:program]}-installer with this command: + + #{@options[:program]}-installer --#{@proxy_hyphenated}-parent-fqdn #{@new_hostname} \\ + --foreman-proxy-foreman-base-url https://#{@new_hostname} \\ + --foreman-proxy-trusted-hosts #{@new_hostname} + + Short hostnames have not been updated, please update those manually.\n + ) + STDOUT.puts "**** Hostname change complete! **** \nIMPORTANT:" + if @foreman_proxy_content + STDOUT.print "You will have to update the Name and URL of the Smart Proxy in #{@options[:program].capitalize} to the new hostname.\n" + else + STDOUT.print successful_message + end + STDOUT.print "" + exit(true) + end + + def warning_message + STDOUT.print("***WARNING*** This script will modify your system. " \ + "You will need to re-register any #{@options[:program]} clients registered to this system after script completion.") + unless @foreman_proxy_content + STDOUT.print(" #{ @plural_proxy } will have to be re-registered and reinstalled. If you are using custom certificates, you " \ + "will have to run the #{@options[:program]}-installer again with custom certificate options after this script completes.") + end + STDOUT.print(" Have you taken the necessary precautions (backups, snapshots, etc...) and want to proceed with " \ + "changing your hostname? \n [y/n]:") + end + + def hammer_cmd(cmd, exit_codes=[0], message=nil) + run_cmd("hammer -u #{@options[:username].shellescape} -p #{@options[:password].shellescape} #{cmd}", exit_codes, message) + end + + def get_default_proxy_id + output = hammer_cmd("--output json capsule info --name #{@old_hostname}", + [0], "Couldn't find default #{@proxy} id") + proxy = JSON.parse(output) + proxy["Id"] + end + + def setup_opt_parser + @opt_parser = OptionParser.new do |opt| + opt.banner = "usage: katello-change-hostname hostname [options]" + opt.separator "" + opt.separator "example:" + opt.separator " katello-change-hostname foo.example.com -u admin -p changeme" + opt.separator "" + opt.separator "options" + + opt.on("-u","--username username","admin username (required)") do |username| + @options[:username] = username + end + + opt.on("-p","--password password","admin password (required)") do |password| + @options[:password] = password + end + + opt.on("-g","--program program","name of the program you are modifying (defaults to #{@default_program})") do |program| + @options[:program] = program + end + + opt.on("-s","--scenario scenario","name of the scenario you are modifying (defaults to #{@last_scenario})") do |scenario| + @options[:scenario] = scenario + end + + if self.disable_system_check_option? + opt.on("-d","--disable-system-checks","runs the installer with --disable-system-checks") do |system_check| + @options[:system_check] = true + end + end + + opt.on("-y", "--assumeyes", "answer yes for all questions") do |confirm| + @options[:confirm] = confirm + end + + if @foreman_proxy_content + opt.on("-c", + "--certs-tar certs_tar", + "the path to the certs tar generated on the #{@default_program} server with the new hostname (required for #{@plural_proxy})") do |certs_tar| + @options[:certs_tar] = certs_tar + end + end + + opt.on("-h","--help","help") do + puts @opt_parser + exit + end + end + @opt_parser.parse! + end + + def run + raise 'Must run as root' unless Process.uid == 0 + + self.precheck + + if @foreman_proxy_content + self.check_for_certs_tar + fpc_installer_args = self.get_fpc_answers + end + + # Get the hostname from your system + @old_hostname = self.get_hostname + + scenario_answers = YAML.load_file("/etc/foreman-installer/scenarios.d/#{@options[:scenario]}-answers.yaml") + + unless @foreman_proxy_content + STDOUT.puts "\nUpdating default #{@proxy}" + proxy_id = self.get_default_proxy_id + # Incorrect error message is piped to /dev/null, can be removed when http://projects.theforeman.org/issues/18186 is fixed + # For the same reason, we accept exit code 65 here. + self.hammer_cmd("capsule update --id #{proxy_id} --url https://#{@new_hostname}:9090 --new-name #{@new_hostname} 2> /dev/null", [0, 65]) + + STDOUT.puts "Updating installation media paths" + old_media = JSON.parse(hammer_cmd("--output json medium list --search 'path ~ //#{@old_hostname}/'")) + old_media.each do |medium| + new_path = URI.parse(medium['Path']) + new_path.host = @new_hostname + new_path = new_path.to_s + hammer_cmd("medium update --id #{medium['Id']} --path #{new_path}") + end + end + + STDOUT.puts "updating hostname in /etc/hostname" + self.run_cmd("sed -i -e 's/#{@old_hostname}/#{@new_hostname}/g' /etc/hostname") + STDOUT.puts "setting hostname" + self.run_cmd("hostnamectl set-hostname #{@new_hostname}") + + # override environment variable (won't be updated until bash login) + ENV['HOSTNAME'] = @new_hostname + + STDOUT.puts "checking if hostname was changed" + if self.get_hostname != @new_hostname + self.fail_with_message("The new hostname was not changed successfully, exiting script") + end + + STDOUT.puts "stopping services" + self.run_cmd("katello-service stop") + + public_dir = "/var/www/html/pub" + public_backup_dir = "#{public_dir}/#{@old_hostname}-#{self.timestamp}.backup" + STDOUT.puts "deleting old certs" + + self.run_cmd("rm -rf /etc/pki/katello-certs-tools{,.bak}") + self.run_cmd("rm -rf #{scenario_answers["foreman_proxy"]["ssl_cert"]}") + self.run_cmd("rm -rf #{scenario_answers["foreman_proxy"]["ssl_key"]}") + self.run_cmd("rm -rf #{scenario_answers["foreman_proxy"]["foreman_ssl_cert"]}") + self.run_cmd("rm -rf #{scenario_answers["foreman_proxy"]["foreman_ssl_key"]}") + self.run_cmd("rm -rf /etc/pki/katello/nssdb") + self.run_cmd("mkdir #{public_backup_dir}") + self.run_cmd("mv #{public_dir}/*.rpm #{public_backup_dir}") + + unless @foreman_proxy_content + self.run_cmd("rm -rf /etc/candlepin/certs/amqp{,.bak}") + self.run_cmd("rm -f /etc/tomcat/keystore") + self.run_cmd("rm -rf /etc/foreman/old-certs") + self.run_cmd("rm -f /etc/pki/katello/keystore") + self.run_cmd("rm -rf #{scenario_answers["foreman"]["client_ssl_cert"]}") + self.run_cmd("rm -rf #{scenario_answers["foreman"]["client_ssl_key"]}") + end + + STDOUT.puts "backed up #{public_dir} to #{public_backup_dir}" + STDOUT.puts "updating hostname in /etc/hosts" + self.run_cmd("sed -i -e 's/#{@old_hostname}/#{@new_hostname}/g' /etc/hosts") + + STDOUT.puts "updating hostname in foreman installer scenarios" + self.run_cmd("sed -i -e 's/#{@old_hostname}/#{@new_hostname}/g' /etc/foreman-installer/scenarios.d/*.yaml") + + STDOUT.puts "removing last_scenario.yml file" + self.run_cmd("rm -rf /etc/foreman-installer/scenarios.d/last_scenario.yaml") + + STDOUT.puts "re-running the installer" + + installer = "#{@options[:program]}-installer --scenario #{@options[:scenario]} -v" + if @foreman_proxy_content + installer << fpc_installer_args + else + installer << " --certs-regenerate=true --foreman-proxy-register-in-foreman true" + end + installer << " --disable-system-checks" if @options[:system_check] + + STDOUT.puts installer + installer_output = self.run_cmd("#{installer}") + installer_success = $?.success? + STDOUT.puts installer_output + + STDOUT.puts "Restarting puppet services" + self.run_cmd("/sbin/service puppet restart") + self.run_cmd("katello-service restart --only puppetserver") + + if installer_success + self.successful_hostname_change_message + else + self.fail_with_message("Something went wrong with the #{@options[:scenario].capitalize} installer! Please check the above output and the corresponding logs") + end + end + end +end diff --git a/katello/katello-backup b/katello/katello-backup index 7645f9b3..93a0a066 100755 --- a/katello/katello-backup +++ b/katello/katello-backup @@ -1,417 +1,18 @@ #!/usr/bin/env ruby -require 'optparse' -require 'fileutils' -require 'date' -require 'yaml' -require 'find' -require 'json' -require 'highline/import' - -EXCLUDED="--exclude goferd,foreman-proxy,squid,smart_proxy_dynflow_core,qdrouterd,qpidd" -DATABASES = ['pulp', 'pgsql', 'mongodb'] - -@options = {} -@dir = nil -@databases = DATABASES.dup - -# keep as variables for easy backporting -FOREMAN_PROXY_CONTENT = "foreman-proxy-content" -FOREMAN_PROXY = "foreman proxy" - -optparse = OptionParser.new do |opts| - opts.banner = "Usage: katello-backup /path/to/dir [options]\n eg: $ katello-backup /tmp/katello-backup" - - opts.on("--skip-pulp-content", "Create backup without Pulp content for debugging only") do |config_only| - @options[:config_only] = config_only - @databases.delete 'pulp' - end - - opts.on("--incremental PREVIOUS_BACKUP_DIR", String, "Backup changes since previous backup") do |dir_path| - opts.abort("Please specify the previous backup directory.") unless dir_path - if File.directory?(dir_path) - @options[:incremental] = dir_path - else - opts.abort("Previous backup directory does not exist: #{dir_path}") - end - end - - opts.on("--online-backup", "Keep services online during backup") do |online| - @options[:online] = online - end - - opts.on("--logical-db-backup", "Also dump full database schema during offline backup") do |logical| - @options[:logical_backup] = logical - end - - opts.on("--snapshot", "Use snapshots of the databases to create backup") do |snapshot| - @options[:snapshot] = snapshot - end - - opts.on("--snapshot-mount-dir SNAPSHOT_MOUNT_LOCATION", String, "Override default directory (/var/snap/) where the snapshots will be mounted") do |mount_dir| - @options[:snapshot_mount] = mount_dir - end - - opts.on("--snapshot-size SNAPSHOT_BLOCK_DEVICE_SIZE", String, "Override default block size (2G)") do |size| - @options[:snapshot_size] = size - end - - opts.on("--features FEATURES", Array, "#{FOREMAN_PROXY.capitalize} features to include in the backup, please specify a list with commas. " \ - "Valid features are tftp, dns, dhcp, openscap, and all.") do |features| - @options[:features] = features.map { |f| f.to_s.downcase } - end - - opts.on("--preserve-directory", "Do not create a time-stamped subdirectory") do |no_subdir| - @options[:no_subdir] = no_subdir - end - - opts.on("-y", "--assumeyes", "Bypass interaction by answering yes") do |confirm| - @options[:confirm] = confirm - end -end - -begin optparse.parse! ARGV - if ARGV.length == 0 - optparse.abort("**** ERROR: Please specify an export directory ****") - elsif ARGV.length != 1 - puts optparse - exit(-1) - end - - @dir = ARGV[0].dup -rescue OptionParser::ParseError => e - puts e - puts optparse - exit -1 -end - -def run_cmd(command, exit_codes=[0]) - result = `#{command}` - unless exit_codes.include?($?.exitstatus) - STDERR.puts "Failed '#{command}' with exit code #{$?.exitstatus}" - cleanup($?.exitstatus) - end - result -end - -def cleanup(exitstatus=-1) - puts "Cleaning up backup folder and starting any stopped services... " - FileUtils.cd("/") - FileUtils.rm_rf @dir unless @options[:no_subdir] - `katello-service start #{EXCLUDED}` unless @options[:online] - puts "Done." - exit(exitstatus) -end - -def specified_features - @options[:features] || [] -end - -def feature_included?(feature) - specified_features.include?(feature) || specified_features.include?("all") -end - -def configure_configs - base_configs = [ - "/etc/foreman-proxy", - "/etc/httpd", - "/etc/foreman-installer", - "/etc/pki/katello", - "/etc/pki/katello-certs-tools", - "/etc/pki/pulp", - "/etc/pulp", - "/etc/puppet", - "/etc/qpid", - "/etc/qpid-dispatch", - "/root/ssl-build", - "/var/www/html/pub", - "/etc/squid", - "/etc/puppetlabs", - '/opt/puppetlabs/puppet/cache/foreman_cache_data', - '/opt/puppetlabs/puppet/ssl/', - '/var/lib/puppet/foreman_cache_data', - '/var/lib/puppet/ssl' - ] - - katello_configs = [ - "/etc/candlepin", - "/etc/foreman", - "/etc/hammer", - "/etc/sysconfig/tomcat*", - "/etc/tomcat*", - "/var/lib/candlepin", - ] - - if @is_foreman_proxy_content - fpc_certs_tar = run_cmd("cat /etc/foreman-installer/scenarios.d/#{FOREMAN_PROXY_CONTENT}-answers.yaml | grep certs_tar | awk '{print $2}'").chomp - unless File.exist?(fpc_certs_tar) - puts "#{FOREMAN_PROXY} certs tar file is not present on the system in path #{fpc_certs_tar} please move the file back to that location or generate a new one on the main server." - cleanup - end - backup_configs = base_configs.push(fpc_certs_tar) - else - backup_configs = base_configs + katello_configs - end - - feature_configs = [] - feature_configs.push("/var/lib/tftpboot") if feature_included?("tftp") - feature_configs += ["/var/named/", "/etc/named*"] if feature_included?("dns") - feature_configs += ["/var/lib/dhcpd", "/etc/dhcp"] if feature_included?("dhcp") - feature_configs.push("/usr/share/xml/scap") if feature_included?("openscap") - - backup_configs + feature_configs -end - -def confirm - unless agree("WARNING: This script will stop your services. Do you want to proceed(y/n)? ") - puts "**** cancelled ****" - FileUtils.rm_rf @dir - exit(-1) - end -end - -def warning - unless agree("*** WARNING: The online backup flag is intended for making a copy of the data\n" \ - "*** for debugging purposes only. The backup routine can not ensure 100% consistency while the\n" \ - "*** backup is taking place as there is a chance there may be data mismatch between\n" \ - "*** Mongo and Postgres databases while the services are live. If you wish to utilize the --online-backup\n" \ - "*** flag for production use you need to ensure that there are no modifications occurring during\n" \ - "*** your backup run.\n\nDo you want to proceed(y/n)? ") - puts "**** cancelled ****" - FileUtils.rm_rf @dir - exit(-1) - end -end - -def create_directories(directory) - @dir = File.join directory, "katello-backup-" + DateTime.now.strftime('%Y%m%d%H%M%S') unless @options[:no_subdir] - puts "Creating backup folder #{@dir}" - FileUtils.mkdir_p @dir - unless @options[:no_subdir] - FileUtils.chown_R nil, 'postgres', @dir if @databases.include? "pgsql" - FileUtils.chmod_R 0770, @dir - end +# check if production install +if __FILE__.start_with?('/sbin/') + UTILS_PATH='/usr/share/katello' +else + UTILS_PATH=File.expand_path('..', __FILE__) end -def snapshot_backup - @mountdir = @options[:snapshot_mount] || "/var/snap" - @snapsize = @options[:snapshot_size] || "2G" - FileUtils.mkdir_p @mountdir - confirm unless @options[:confirm] - run_cmd("katello-service stop #{EXCLUDED}") - create_and_mount_snapshots - run_cmd("katello-service start #{EXCLUDED}") - backup_and_destroy_snapshots -end - -def create_and_mount_snapshots - @databases.each do |database| - puts "Creating #{database} snapshot" - lv_info = get_lv_info(database) - run_cmd("lvcreate -n#{database}-snap -L#{@snapsize} -s #{lv_info[0]}") - - mount_location = File.join(@mountdir, database) - FileUtils.mkdir_p mount_location - puts "Mounting #{database} snapshot on #{mount_location}" - options = lv_info[1] == 'xfs' ? "-onouuid,ro" : "-oro" - run_cmd("mount #{get_snapshot_location(database)} #{mount_location} #{options}") - end -end - -def get_snapshot_location(database) - run_cmd("lvs --noheadings -o lv_path -S lv_name=#{database}-snap").strip -end - -def get_lv_info(database) - target = database - target = File.join(database, 'data') if target == 'pgsql' - run_cmd("findmnt -n --target #{File.join('/var/lib', target)} -o SOURCE,FSTYPE").split -end +require File.join(UTILS_PATH, 'backup.rb') -def get_base_directory(database) - target = "" - case database - when 'pulp' - target = '0005_puppet_module_name_change.txt' - when 'pgsql' - target = 'postgresql.conf' - when 'mongodb' - target = 'mongod.lock' - end +katello_backup = KatelloUtilities::Backup.new("foreman-proxy-content", "foreman proxy", "katello") - result = nil - Find.find(File.join(@mountdir, database)) do |path| - result = File.dirname(path) if File.basename(path) == target - end - result +if katello_backup.accepted_scenarios.include? katello_backup.last_scenario + katello_backup.run +else + STDOUT.puts katello_backup.error_message end - -def backup_and_destroy_snapshots - @databases.each do |database| - basedir = get_base_directory(database) - puts "Backing up #{database} from #{basedir}" - offline_backup(database, basedir) - - puts "Unmounting #{database} snapshot" - run_cmd("umount #{File.join(@mountdir, database)}") - - snapshot_location = get_snapshot_location(database) - puts "Removing snapshot #{snapshot_location}" - run_cmd("lvremove -f #{snapshot_location}") - end -end - -def online_backup(database) - case database - when 'pulp' - FileUtils.cd '/var/lib/pulp' do - puts "Backing up Pulp data... " - matching = false - until matching - checksum1 = run_cmd("find . -printf '%T@\n' | md5sum") - create_pulp_data_tar - checksum2 = run_cmd("find . -printf '%T@\n' | md5sum") - matching = (checksum1 == checksum2) - end - end - puts "Done." - when 'pgsql' - puts "Backing up postgres online schema... " - run_cmd("runuser - postgres -c 'pg_dumpall -g > #{File.join(@dir, 'pg_globals.dump')}'") - run_cmd("runuser - postgres -c 'pg_dump -Fc foreman > #{File.join(@dir, 'foreman.dump')}'") - run_cmd("runuser - postgres -c 'pg_dump -Fc candlepin > #{File.join(@dir, 'candlepin.dump')}'") - puts "Done." - when 'mongodb' - puts "Backing up mongo online schema... " - run_cmd("mongodump --host localhost --out #{File.join(@dir, 'mongo_dump')}") - puts "Done." - end -end - -def offline_backup(database, dir_path = nil) - case database - when 'pulp' - dir_path ||= '/var/lib/pulp' - FileUtils.cd dir_path do - puts "Backing up Pulp data... " - create_pulp_data_tar - puts "Done." - end - when 'mongodb' - dir_path ||= '/var/lib/mongodb' - FileUtils.cd dir_path do - puts "Backing up mongo db... " - run_cmd("tar --selinux --create --file=#{File.join(@dir, 'mongo_data.tar')} --listed-incremental=#{File.join(@dir, '.mongo.snar')} --exclude=mongod.lock --transform 's,^,var/lib/mongodb/,S' -S *") - puts "Done." - end - when 'pgsql' - dir_path ||= '/var/lib/pgsql/data' - FileUtils.cd dir_path do - puts "Backing up postgres db..." - run_cmd("tar --selinux --create --file=#{File.join(@dir, 'pgsql_data.tar')} --listed-incremental=#{File.join(@dir, '.postgres.snar')} --transform 's,^,var/lib/pgsql/data/,S' -S *") - puts "Done." - end - end -end - -def compress_files - psql = spawn('gzip', 'pgsql_data.tar', '-f') if @databases.include? "pgsql" - mongo = spawn('gzip', 'mongo_data.tar', '-f') - Process.wait(psql) if @databases.include? "pgsql" - Process.wait(mongo) -end - -def plugin_list - if @is_foreman_proxy_content - JSON.parse(run_cmd("curl -k https://$(hostname):9090/features")) - else - plugins = [] - plugin_list = run_cmd("foreman-rake plugin:list | grep 'Foreman plugin: '", [0,1]).lines - plugin_list.each do |line| - plugin = line.split - plugins << "#{plugin[2].chop}-#{plugin[3].chop}" - end - plugins - end -end - -def generate_metadata - puts "Generating metadata ... " - os_version = run_cmd("cat /etc/redhat-release").chomp - plugins = plugin_list - rpms = run_cmd("rpm -qa").split("\n") - system_facts = {os_version: os_version, plugin_list: plugins, rpms: rpms} - File.open('metadata.yml', 'w') do |metadata_file| - metadata_file.puts system_facts.to_yaml - end - puts "Done." -end - -def create_pulp_data_tar - run_cmd("tar --selinux --create --file=#{File.join(@dir, 'pulp_data.tar')} --exclude=var/lib/pulp/katello-export --listed-incremental=#{File.join(@dir, '.pulp.snar')} --transform 's,^,var/lib/pulp/,S' -S *") -end - -def backup_config_files - puts "Backing up config files... " - run_cmd("tar --selinux --create --gzip --file=#{File.join(@dir, 'config_files.tar.gz')} --listed-incremental=#{File.join(@dir, '.config.snar')} #{configure_configs.join(' ')} 2>/dev/null", [0,2]) - puts "Done." -end - -def validate_directory - unless system("sudo -u postgres test -w #{@dir}") - puts "****cancelled****" - puts "Postgres user needs write access to the backup directory" - puts "Please select a directory, such as /tmp or /var/tmp which allows Postgres write access" - cleanup - end -end - -if @dir.nil? - puts "**** ERROR: Please specify an export directory ****" - puts optparse - exit(-1) -end - -puts "Starting backup: #{Time.now}" -@is_foreman_proxy_content = !run_cmd("rpm -qa | grep foreman-proxy-content", [0,1]).empty? -@databases.delete 'pgsql' if @is_foreman_proxy_content -create_directories(@dir.dup) -validate_directory - -Dir.chdir(@dir) do - generate_metadata - if @options[:incremental] - FileUtils.cp Dir.glob(File.join(@options[:incremental], '.*.snar')), @dir - elsif @options[:no_subdir] - FileUtils.rm Dir.glob(File.join(@dir, '.*.snar')) - end - backup_config_files - - if @options[:logical_backup] - @databases.each do |database| - online_backup(database) - end - end - - if @options[:snapshot] - snapshot_backup - compress_files - elsif @options[:online] - warning unless @options[:confirm] - @databases.each do |database| - online_backup(database) - end - else - confirm unless @options[:confirm] - run_cmd("katello-service stop #{EXCLUDED}") - @databases.each do |database| - offline_backup(database) - end - run_cmd("katello-service start #{EXCLUDED}") - compress_files - end -end - -puts "Done with backup: #{Time.now}" -puts "**** BACKUP Complete, contents can be found in: #{@dir} ****" - diff --git a/katello/katello-change-hostname b/katello/katello-change-hostname index 36b51503..c3f3a03b 100755 --- a/katello/katello-change-hostname +++ b/katello/katello-change-hostname @@ -1,350 +1,18 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby -require "socket" -require "optparse" -require "rubygems" -require 'yaml' -require 'shellwords' -require "json" -require "uri" - -raise 'Must run as root' unless Process.uid == 0 - -def run_cmd(command, exit_codes=[0], message=nil) - result = `#{command}` - unless exit_codes.include?($?.exitstatus) - STDOUT.puts result - STDOUT.puts message if message - fail_with_message("Failed '#{command}' with exit code #{$?.exitstatus}") - end - result -end - -def disable_system_check_option? - katello_installer_rpm = run_cmd("rpm -qa | grep katello-installer") - katello_installer_version = katello_installer_rpm[/(\d+\.)(\d+\.)(\d+)/] - Gem::Version.new(katello_installer_version) >= Gem::Version.new("3.2.0") -end - -def get_default_program - case DEFAULT_SCENARIO - when "katello" - return "foreman" - when FOREMAN_PROXY_CONTENT - return "foreman" - else - return DEFAULT_SCENARIO - end -end - -# keep as variables for easy changing in backports -FOREMAN_PROXY_CONTENT = "foreman-proxy-content" -DEFAULT_SCENARIO = File.basename(File.readlink("/etc/foreman-installer/scenarios.d/last_scenario.yaml")).split(".")[0] -DEFAULT_PROGRAM = get_default_program -@proxy = "Foreman Proxy" -@plural_proxy = "Foreman Proxies" - -@options = {} -@options[:program] = DEFAULT_PROGRAM -@options[:scenario] = DEFAULT_SCENARIO -@options[:system_check] = false -@foreman_proxy_content = @options[:scenario] == FOREMAN_PROXY_CONTENT - -@opt_parser = OptionParser.new do |opt| - opt.banner = "Usage: katello-change-hostname HOSTNAME [OPTIONS]" - opt.separator "" - opt.separator "Example:" - opt.separator " katello-change-hostname foo.example.com -u admin -p changeme" - opt.separator "" - opt.separator "Options" - - opt.on("-u","--username USERNAME","admin username (required)") do |username| - @options[:username] = username - end - - opt.on("-p","--password PASSWORD","admin password (required)") do |password| - @options[:password] = password - end - - opt.on("-g","--program PROGRAM","name of the program you are modifying (defaults to #{DEFAULT_PROGRAM})") do |program| - @options[:program] = program - end - - opt.on("-S","--scenario SCENARIO","name of the scenario you are modifying (defaults to #{DEFAULT_SCENARIO})") do |scenario| - @options[:scenario] = scenario - end - - if disable_system_check_option? - opt.on("-d","--disable-system-checks","runs the installer with --disable-system-checks") do |system_check| - @options[:system_check] = true - end - end - - opt.on("-y", "--assumeyes", "Answer yes for all questions") do |confirm| - @options[:confirm] = confirm - end - - if @foreman_proxy_content - opt.on("-c", - "--certs-tar CERTS_TAR", - "the path to the certs tar generated on the #{DEFAULT_PROGRAM} server with the new hostname (required for #{@plural_proxy})") do |certs_tar| - @options[:certs_tar] = certs_tar - end - end - - opt.on("-h","--help","help") do - puts @opt_parser - exit - end -end -@opt_parser.parse! - -def timestamp - Time.now.strftime("%Y%m%d%H%M") -end - -def get_hostname - Socket.gethostname.chomp -end - -def yesno - begin - system("stty raw -echo") - str = STDIN.getc - ensure - system("stty -raw echo") - end - if str.chr.downcase == "y" - return true - elsif str.chr.downcase == "n" - return false - else - puts "Invalid Character. Try again: [y/n]" - yesno - end -end - -def fail_with_message(message, opt_parser=nil) - STDOUT.puts message - puts opt_parser if opt_parser - exit(false) -end - -def check_for_certs_tar - STDOUT.puts "Checking for certs tarball" - if @options[:certs_tar] - if File.file?(@options[:certs_tar]) - true - else - fail_with_message("#{@options[:certs_tar]} does not exist! Please check the file path and try again") - end - else - fail_with_message("You must specify --certs-tar argument when on a #{@proxy}." \ - " These can be generated on the #{DEFAULT_PROGRAM} server using foreman-proxy-certs-generate and copied to this machine.") - end -end - -def get_fpc_answers - register_in_foreman = false - certs_tar = @options[:certs_tar] - " --foreman-proxy-register-in-foreman #{register_in_foreman} --foreman-proxy-content-certs-tar #{certs_tar}" -end - -def precheck - unless @options[:username] && @options[:password] - fail_with_message("Username and/or Password options are missing!", @opt_parser) - end - - if ARGV[0] && ARGV.count >= 1 - @new_hostname = ARGV[0] - else - fail_with_message("Please specify a hostname.", @opt_parser) - end - - STDOUT.puts "\nChecking hostname validity" - # This regex is an approximation of a hostname, it will handle most invalid hostnames and typos. - # Taken from https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html - hostname_regex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/ - unless hostname_regex === @new_hostname - fail_with_message("#{@new_hostname} is not a valid fully qualified domain name, please use a valid FQDN and try again. " \ - "No changes have been made to your system."); - end - - unless @foreman_proxy_content - STDOUT.puts "\nChecking overall health of server" - run_cmd("hammer ping", [0], "There is a problem with the server, please check 'hammer ping'") - STDOUT.puts "\nChecking credentials" - hammer_cmd("capsule list", [0], "There is a problem with the credentials, please retry") - end - - if @options[:confirm] - response = true - else - STDOUT.print(warning_message) - response = yesno - end - - unless response - fail_with_message("Hostname change aborted, no changes have been made to your system") - end -end - -def successful_hostname_change_message -# the following multi-line string isn't indented because the indents are taken literally. -successful_message = %( -If you want to use custom certificates, re-run the #{@options[:program]}-installer with custom certificate options. - -You will have to install the new bootstrap rpm and reregister all clients and #{@plural_proxy} with subscription-manager -(update organization and environment arguments appropriately): - - yum remove -y katello-ca-consumer* - rpm -Uvh http://#{@new_hostname}/pub/katello-ca-consumer-latest.noarch.rpm - subscription-manager register --org="Default_Organization" --environment="Library" --force - -Then reattach subscriptions to the client(s) and run: - - subscription-manager refresh - yum repolist - - -On all #{@plural_proxy}, you will need to re-run the foreman-installer with this command: - -foreman-installer --foreman-proxy-content-parent-fqdn #{@new_hostname} \\ - --foreman-proxy-foreman-base-url https://#{@new_hostname} \\ - --foreman-proxy-trusted-hosts #{@new_hostname} - -Short hostnames have not been updated, please update those manually.\n -) - STDOUT.puts "**** Hostname change complete! **** \nIMPORTANT:" - if @foreman_proxy_content - STDOUT.print "You will have to update the Name and URL of the Smart Proxy in #{@options[:program].capitalize} to the new hostname.\n" - else - STDOUT.print successful_message - end - STDOUT.print "" - exit(true) -end - -def warning_message - STDOUT.print("***WARNING*** This script will modify your system. " \ - "You will need to re-register any #{@options[:program]} clients registered to this system after script completion.") - unless @foreman_proxy_content - STDOUT.print(" #{ @plural_proxy } will have to be re-registered and reinstalled. If you are using custom certificates, you " \ - "will have to run the #{@options[:program]}-installer again with custom certificate options after this script completes.") - end - STDOUT.print(" Have you taken the necessary precautions (backups, snapshots, etc...) and want to proceed with " \ - "changing your hostname? \n [y/n]:") -end - -def hammer_cmd(cmd, exit_codes=[0], message=nil) - run_cmd("hammer -u #{@options[:username].shellescape} -p #{@options[:password].shellescape} #{cmd}", exit_codes, message) -end - -def get_default_proxy_id - output = hammer_cmd("--output json capsule info --name #{@old_hostname}", - [0], "Couldn't find default #{@proxy} id") - proxy = JSON.parse(output) - proxy["Id"] -end - -precheck - -if @foreman_proxy_content - check_for_certs_tar - fpc_installer_args = get_fpc_answers -end - -# Get the hostname from your system -@old_hostname = get_hostname - -scenario_answers = YAML.load_file("/etc/foreman-installer/scenarios.d/#{@options[:scenario]}-answers.yaml") - -unless @foreman_proxy_content - STDOUT.puts "\nUpdating default #{@proxy}" - proxy_id = get_default_proxy_id - # Incorrect error message is piped to /dev/null, can be removed when http://projects.theforeman.org/issues/18186 is fixed - # For the same reason, we accept exit code 65 here. - hammer_cmd("capsule update --id #{proxy_id} --url https://#{@new_hostname}:9090 --new-name #{@new_hostname} 2> /dev/null", [0, 65]) - - STDOUT.puts "Updating installation media paths" - old_media = JSON.parse(hammer_cmd("--output json medium list --search 'path ~ //#{@old_hostname}/'")) - old_media.each do |medium| - new_path = URI.parse(medium['Path']) - new_path.host = @new_hostname - new_path = new_path.to_s - hammer_cmd("medium update --id #{medium['Id']} --path #{new_path}") - end -end - -STDOUT.puts "updating hostname in /etc/hostname" -run_cmd("sed -i -e 's/#{@old_hostname}/#{@new_hostname}/g' /etc/hostname") -STDOUT.puts "setting hostname" -run_cmd("hostnamectl set-hostname #{@new_hostname}") - -# override environment variable (won't be updated until bash login) -ENV['HOSTNAME'] = @new_hostname - -STDOUT.puts "checking if hostname was changed" -if get_hostname != @new_hostname - fail_with_message("The new hostname was not changed successfully, exiting script") -end - -STDOUT.puts "stopping services" -run_cmd("katello-service stop") - -public_dir = "/var/www/html/pub" -public_backup_dir = "#{public_dir}/#{@old_hostname}-#{timestamp}.backup" -STDOUT.puts "deleting old certs" - -run_cmd("rm -rf /etc/pki/katello-certs-tools{,.bak}") -run_cmd("rm -rf #{scenario_answers["foreman_proxy"]["ssl_cert"]}") -run_cmd("rm -rf #{scenario_answers["foreman_proxy"]["ssl_key"]}") -run_cmd("rm -rf #{scenario_answers["foreman_proxy"]["foreman_ssl_cert"]}") -run_cmd("rm -rf #{scenario_answers["foreman_proxy"]["foreman_ssl_key"]}") -run_cmd("rm -rf /etc/pki/katello/nssdb") -run_cmd("mkdir #{public_backup_dir}") -run_cmd("mv #{public_dir}/*.rpm #{public_backup_dir}") - -unless @foreman_proxy_content - run_cmd("rm -rf /etc/candlepin/certs/amqp{,.bak}") - run_cmd("rm -f /etc/tomcat/keystore") - run_cmd("rm -rf /etc/foreman/old-certs") - run_cmd("rm -f /etc/pki/katello/keystore") - run_cmd("rm -rf #{scenario_answers["foreman"]["client_ssl_cert"]}") - run_cmd("rm -rf #{scenario_answers["foreman"]["client_ssl_key"]}") -end - -STDOUT.puts "backed up #{public_dir} to #{public_backup_dir}" -STDOUT.puts "updating hostname in /etc/hosts" -run_cmd("sed -i -e 's/#{@old_hostname}/#{@new_hostname}/g' /etc/hosts") - -STDOUT.puts "updating hostname in foreman installer scenarios" -run_cmd("sed -i -e 's/#{@old_hostname}/#{@new_hostname}/g' /etc/foreman-installer/scenarios.d/*.yaml") - -STDOUT.puts "removing last_scenario.yml file" -run_cmd("rm -rf /etc/foreman-installer/scenarios.d/last_scenario.yaml") - -STDOUT.puts "re-running the installer" - -installer = "#{@options[:program]}-installer --scenario #{@options[:scenario]} -v" -if @foreman_proxy_content - installer << fpc_installer_args +# check if production install +if __FILE__.start_with?('/sbin/') + UTILS_PATH='/usr/share/katello' else - installer << " --certs-regenerate=true --foreman-proxy-register-in-foreman true" + UTILS_PATH=File.expand_path('..', __FILE__) end -installer << " --disable-system-checks" if @options[:system_check] -STDOUT.puts installer -installer_output = run_cmd("#{installer}") -installer_success = $?.success? -STDOUT.puts installer_output +require File.join(UTILS_PATH, 'hostname-change.rb') -STDOUT.puts "Restarting puppet services" -run_cmd("/sbin/service puppet restart") -run_cmd("katello-service restart --only puppetserver") +hostname_change = KatelloUtilities::HostnameChange.new("Foreman Proxy", "Foreman Proxies", "foreman-proxy-content") -if installer_success - successful_hostname_change_message +if hostname_change.accepted_scenarios.include? hostname_change.last_scenario + hostname_change.run else - fail_with_message("Something went wrong with the #{@options[:scenario].capitalize} installer! Please check the above output and the corresponding logs") + STDOUT.puts hostname_change.error_message end diff --git a/katello/katello-restore b/katello/katello-restore index b59fcc4f..87d3ec21 100755 --- a/katello/katello-restore +++ b/katello/katello-restore @@ -1,257 +1,18 @@ #!/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 = "foreman-proxy-content" # keep this variable easy to change for backporting - -confirmed = false -@disable_system_checks = false -@foreman_proxy_content = !`rpm -qa | grep foreman-proxy-content`.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 | foreman-installer -v --reset" - if @foreman_proxy_content - installer << " --scenario #{FOREMAN_PROXY_CONTENT} --foreman-proxy-register-in-foreman false" - else - installer << " --scenario katello" - 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 hostname_check - # make sure that the system hostname is the same as the backup - backup_hostname = run_cmd("tar zxf #{File.join(@dir, 'config_files.tar.gz')} etc/httpd/conf/httpd.conf --to-stdout | grep ServerName | awk {'print $2'} | tr -d '\"'").chomp - hostname = Socket.gethostname.chomp - backup_hostname == hostname +# check if production install +if __FILE__.start_with?('/sbin/') + UTILS_PATH='/usr/share/katello' +else + UTILS_PATH=File.expand_path('..', __FILE__) 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 = Dir.exists?(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')) +require File.join(UTILS_PATH, 'restore.rb') - unless @config_exists - puts "Cannot find the required config_files.tar.gz file in #{@dir}" - exit(-1) - end - - unless hostname_check - puts "The hostname in the backup does not match the hostname of the system" - 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 +katello_restore = KatelloUtilities::Restore.new("foreman-proxy-content", "katello") -if backup_valid? - confirmed ? restore : confirm +if katello_restore.accepted_scenarios.include? katello_restore.last_scenario + katello_restore.run else - display_backup_options + STDOUT.puts katello_restore.error_message end diff --git a/katello/katello.spec b/katello/katello.spec index 78b23de8..9f1057e3 100644 --- a/katello/katello.spec +++ b/katello/katello.spec @@ -27,6 +27,10 @@ Source10: katello-clean-empty-puppet-environments Source11: katello-change-hostname Source12: katello-repository-publish-check Source13: katello-change-hostname.8.asciidoc +Source14: restore.rb +Source15: backup.rb +Source16: hostname-change.rb +Source17: helper.rb BuildRequires: asciidoc BuildRequires: util-linux @@ -88,6 +92,13 @@ install -m 755 %{SOURCE3} %{buildroot}%{_sysconfdir}/cron.weekly/katello-remove- install -m 755 %{SOURCE10} %{buildroot}%{_sysconfdir}/cron.weekly/katello-clean-empty-puppet-environments install -m 755 %{SOURCE12} %{buildroot}%{_sysconfdir}/cron.daily/katello-repository-publish-check +# symlink script libraries +mkdir -p %{buildroot}%{_datarootdir}/katello +install -m 644 %{SOURCE14} %{buildroot}%{_datarootdir}/katello/restore.rb +install -m 644 %{SOURCE15} %{buildroot}%{_datarootdir}/katello/backup.rb +install -m 644 %{SOURCE16} %{buildroot}%{_datarootdir}/katello/hostname-change.rb +install -m 644 %{SOURCE17} %{buildroot}%{_datarootdir}/katello/helper.rb + # install important scripts mkdir -p %{buildroot}%{_bindir} mkdir -p %{buildroot}%{_sbindir} @@ -136,6 +147,10 @@ Common runtime components of %{name} %{_sbindir}/qpid-core-dump %{_sbindir}/katello-change-hostname %{_mandir}/man8/katello-change-hostname.8* +%{_datarootdir}/katello/restore.rb +%{_datarootdir}/katello/backup.rb +%{_datarootdir}/katello/hostname-change.rb +%{_datarootdir}/katello/helper.rb %config(missingok) %{_sysconfdir}/cron.weekly/katello-clean-empty-puppet-environments %config(missingok) %{_sysconfdir}/cron.weekly/katello-remove-orphans %config(missingok) %{_sysconfdir}/cron.daily/katello-repository-publish-check diff --git a/katello/restore.rb b/katello/restore.rb new file mode 100644 index 00000000..10b5e775 --- /dev/null +++ b/katello/restore.rb @@ -0,0 +1,267 @@ +require 'socket' +require 'optparse' +require 'fileutils' +require 'date' +require_relative "helper.rb" + +module KatelloUtilities + class Restore + include ::KatelloUtilities::Helper + + def initialize(foreman_proxy_content, program, accepted_scenarios=nil) + @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 = foreman_proxy_content + @accepted_scenarios = accepted_scenarios + @program = program + + @confirmed = false + @disable_system_checks = false + @is_foreman_proxy_content = !`rpm -qa | grep foreman-proxy-content`.empty? + @skip_register = false + + setup_opt_parser + end + + def setup_opt_parser + @optparse = OptionParser.new do |opts| + opts.banner = "Usage: #{@program}-restore /path/to/dir [options]\n eg: $ #{@program}-restore /tmp/backup/#{@program}-backup-20171002150106" + + 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 + end + @optparse + end + + def parse_options + begin @optparse.parse! ARGV + if ARGV.length == 0 + @optparse.abort("**** ERROR: Please specify the backup directory to restore ****") + elsif ARGV.length != 1 + puts @optparse + exit(-1) + end + + @dir = ARGV.pop.dup + unless File.directory?(@dir) + @optparse.abort("Backup directory does not exist: #{@dir}") + end + end + end + + def set_file_security + puts "Setting file security" + run_cmd("restorecon -Rn /") + 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 | foreman-installer -v --reset" + if @is_foreman_proxy_content + installer << " --scenario #{@foreman_proxy_content} --foreman-proxy-register-in-foreman false" + else + installer << " --scenario #{@program}" + 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 @is_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 @is_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 @is_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 hostname_check + # make sure that the system hostname is the same as the backup + backup_hostname = run_cmd("tar zxf #{File.join(@dir, 'config_files.tar.gz')} etc/httpd/conf/httpd.conf --to-stdout | grep ServerName | awk {'print $2'} | tr -d '\"'").chomp + hostname = Socket.gethostname.chomp + backup_hostname == hostname + 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 = Dir.exists?(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 + + unless hostname_check + puts "The hostname in the backup does not match the hostname of the system" + exit(-1) + end + + return true if valid_logical_backup + + if @is_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 + + def run + parse_options + if backup_valid? + @confirmed ? restore : confirm + else + display_backup_options + end + end + end +end