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