lib/tasks/geo_replication_backup.rake (99 lines of code) (raw):

# frozen_string_literal: true require 'tempfile' namespace :geo do desc 'Backup primary database into secondary postgresql database and enable standby server' task :replication_backup do ReplicationBackup.new.execute end end # Handles pg_basebackup logic when enabling Geo replication on the secondary site class ReplicationBackup attr_reader :backup_dir, :current_data_dir, :primary_host, :primary_port def initialize @backup_dir = "#{Dir.pwd}/tmp/geobackup/data_#{Time.now.utc.strftime('%Y%m%d%H%M%S')}" @current_data_dir = GDK.config.postgresql.data_dir end def execute set_primary_host_and_port backup_and_empty_data_dir process_backup replace_or_update_gitlab_conf configure_standby_server start_postgres_service cleanup_temp_dir rescue StandardError => e GDK::Output.error("Backup failed: #{e.message}.") restore_data_in_postgresql_dir if Dir.exist?(backup_dir) && !Dir.empty?(backup_dir) end private def set_primary_host_and_port @primary_host = run_command_for_stdout("#{postgresql_primary_dir}/../", 'gdk config get postgresql.host', 'primary host not found') @primary_port = run_command_for_stdout("#{postgresql_primary_dir}/../", 'gdk config get postgresql.port', 'primary port not found') end def backup_and_empty_data_dir GDK::Output.info('Backing up the data folder...') FileUtils.mkdir_p(backup_dir) FileUtils.cd(current_data_dir) do |data_dir| FileUtils.mv(Dir.children(data_dir), backup_dir, secure: true) end end def process_backup GDK::Output.info('Processing base backup...') run_command("pg_basebackup -h #{primary_host} -p #{primary_port} -D #{current_data_dir} \ -U gitlab_replication --wal-method=fetch -P -R", 'Backup failed!') end def replace_or_update_gitlab_conf if File.exist?("#{backup_dir}/gitlab.conf") FileUtils.cp("#{backup_dir}/gitlab.conf", current_data_dir) else update_gitlab_conf end end def update_gitlab_conf GDK::Output.info('Updating the Geo replication port...') db_port = GDK.config.postgresql.port template = GDK.config.gdk_root.join('support/templates/postgresql/data/gitlab.conf.erb') GDK::Templates::ErbRenderer.new(template, port: db_port).render(current_data_dir.join('gitlab.conf')) end def configure_standby_server GDK::Output.info('Configuring the standby server...') run_command("./support/postgresql-standby-server #{primary_host} #{primary_port}", 'configuration of the standby server failed') end def start_postgres_service GDK::Output.info('Starting the service...') run_command('gdk start postgresql', 'could not start the postgresql service') end def cleanup_temp_dir GDK::Output.info('Cleaning up temp files...') FileUtils.remove_dir(backup_dir) end def postgresql_primary_dir @postgresql_primary_dir ||= File.realdirpath('postgresql-primary') end def restore_data_in_postgresql_dir FileUtils.remove_dir(current_data_dir) FileUtils.mkdir_p(current_data_dir, mode: 0o700) FileUtils.cd(backup_dir) do |data_dir| FileUtils.mv(Dir.children(data_dir), current_data_dir, secure: true) end cleanup_temp_dir GDK::Output.info("#{current_data_dir} has been restored as before the backup was attempted") end def run_command(command, error_message) return shell_command(command, error_message) unless GDK::Dependencies.bundler_loaded? Bundler.with_unbundled_env do shell_command(command, error_message) end end def shell_command(command, error_message) sh = GDK::Shellout.new(command.split, chdir: GDK.config.gdk_root).execute raise StandardError, error_message unless sh.success? end def run_command_for_stdout(dir, cmd, error_message) # Begin hack # The shell command above returns exit 0 but no output when running "cd <primary_dir> && cmd" as a command # It also does not work to set a chdir options - the gdk command is still run from the current, secondary gdk. sh = GDK::Shellout.new("cd #{dir} && #{cmd}") # End hack sh.execute raise StandardError, error_message unless sh.success? sh.read_stdout end end