Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/backup/manager.rb')
-rw-r--r--lib/backup/manager.rb257
1 files changed, 146 insertions, 111 deletions
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 5b393cf9477..6e90824fce2 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -2,43 +2,84 @@
module Backup
class Manager
- ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs terraform_state registry packages].freeze
- FOLDERS_TO_BACKUP = %w[repositories db].freeze
FILE_NAME_SUFFIX = '_gitlab_backup.tar'
+ MANIFEST_NAME = 'backup_information.yml'
+
+ TaskDefinition = Struct.new(
+ :destination_path, # Where the task should put its backup file/dir.
+ :destination_optional, # `true` if the destination might not exist on a successful backup.
+ :cleanup_path, # Path to remove after a successful backup. Uses `destination_path` when not specified.
+ :task,
+ keyword_init: true
+ )
attr_reader :progress
- def initialize(progress)
+ def initialize(progress, definitions: nil)
@progress = progress
max_concurrency = ENV.fetch('GITLAB_BACKUP_MAX_CONCURRENCY', 1).to_i
max_storage_concurrency = ENV.fetch('GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY', 1).to_i
-
- @tasks = {
- 'db' => Database.new(progress),
- 'repositories' => Repositories.new(progress,
- strategy: repository_backup_strategy,
- max_concurrency: max_concurrency,
- max_storage_concurrency: max_storage_concurrency),
- 'uploads' => Uploads.new(progress),
- 'builds' => Builds.new(progress),
- 'artifacts' => Artifacts.new(progress),
- 'pages' => Pages.new(progress),
- 'lfs' => Lfs.new(progress),
- 'terraform_state' => TerraformState.new(progress),
- 'registry' => Registry.new(progress),
- 'packages' => Packages.new(progress)
+ force = ENV['force'] == 'yes'
+ incremental = Gitlab::Utils.to_boolean(ENV['INCREMENTAL'], default: false)
+
+ @definitions = definitions || {
+ 'db' => TaskDefinition.new(
+ destination_path: 'db/database.sql.gz',
+ cleanup_path: 'db',
+ task: Database.new(progress, force: force)
+ ),
+ 'repositories' => TaskDefinition.new(
+ destination_path: 'repositories',
+ destination_optional: true,
+ task: Repositories.new(progress,
+ strategy: repository_backup_strategy(incremental),
+ max_concurrency: max_concurrency,
+ max_storage_concurrency: max_storage_concurrency)
+ ),
+ 'uploads' => TaskDefinition.new(
+ destination_path: 'uploads.tar.gz',
+ task: Uploads.new(progress)
+ ),
+ 'builds' => TaskDefinition.new(
+ destination_path: 'builds.tar.gz',
+ task: Builds.new(progress)
+ ),
+ 'artifacts' => TaskDefinition.new(
+ destination_path: 'artifacts.tar.gz',
+ task: Artifacts.new(progress)
+ ),
+ 'pages' => TaskDefinition.new(
+ destination_path: 'pages.tar.gz',
+ task: Pages.new(progress)
+ ),
+ 'lfs' => TaskDefinition.new(
+ destination_path: 'lfs.tar.gz',
+ task: Lfs.new(progress)
+ ),
+ 'terraform_state' => TaskDefinition.new(
+ destination_path: 'terraform_state.tar.gz',
+ task: TerraformState.new(progress)
+ ),
+ 'registry' => TaskDefinition.new(
+ destination_path: 'registry.tar.gz',
+ task: Registry.new(progress)
+ ),
+ 'packages' => TaskDefinition.new(
+ destination_path: 'packages.tar.gz',
+ task: Packages.new(progress)
+ )
}.freeze
end
def create
- @tasks.keys.each do |task_name|
+ @definitions.keys.each do |task_name|
run_create_task(task_name)
end
- write_info
+ write_backup_information
- if ENV['SKIP'] && ENV['SKIP'].include?('tar')
+ if skipped?('tar')
upload
else
pack
@@ -54,21 +95,23 @@ module Backup
end
def run_create_task(task_name)
- task = @tasks[task_name]
+ definition = @definitions[task_name]
- puts_time "Dumping #{task.human_name} ... ".color(:blue)
+ build_backup_information
+ puts_time "Dumping #{definition.task.human_name} ... ".color(:blue)
- unless task.enabled
+ unless definition.task.enabled
puts_time "[DISABLED]".color(:cyan)
return
end
- if ENV["SKIP"] && ENV["SKIP"].include?(task_name)
+ if skipped?(task_name)
puts_time "[SKIPPED]".color(:cyan)
return
end
- task.dump
+ definition.task.dump(File.join(Gitlab.config.backup.path, definition.destination_path))
+
puts_time "done".color(:green)
rescue Backup::DatabaseBackupError, Backup::FileBackupError => e
@@ -77,42 +120,11 @@ module Backup
def restore
cleanup_required = unpack
+ read_backup_information
verify_backup_version
- unless skipped?('db')
- begin
- unless ENV['force'] == 'yes'
- warning = <<-MSG.strip_heredoc
- Be sure to stop Puma, Sidekiq, and any other process that
- connects to the database before proceeding. For Omnibus
- installs, see the following link for more information:
- https://docs.gitlab.com/ee/raketasks/backup_restore.html#restore-for-omnibus-gitlab-installations
-
- Before restoring the database, we will remove all existing
- tables to avoid future upgrade problems. Be aware that if you have
- custom tables in the GitLab database these tables and all data will be
- removed.
- MSG
- puts warning.color(:red)
- Gitlab::TaskHelpers.ask_to_continue
- puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.color(:yellow)
- sleep(5)
- end
-
- # Drop all tables Load the schema to ensure we don't have any newer tables
- # hanging out from a failed upgrade
- puts_time 'Cleaning the database ... '.color(:blue)
- Rake::Task['gitlab:db:drop_tables'].invoke
- puts_time 'done'.color(:green)
- run_restore_task('db')
- rescue Gitlab::TaskAbortedByUserError
- puts "Quitting...".color(:red)
- exit 1
- end
- end
-
- @tasks.except('db').keys.each do |task_name|
- run_restore_task(task_name) unless skipped?(task_name)
+ @definitions.keys.each do |task_name|
+ run_restore_task(task_name) if !skipped?(task_name) && enabled_task?(task_name)
end
Rake::Task['gitlab:shell:setup'].invoke
@@ -130,30 +142,71 @@ module Backup
end
def run_restore_task(task_name)
- task = @tasks[task_name]
+ definition = @definitions[task_name]
- puts_time "Restoring #{task.human_name} ... ".color(:blue)
+ read_backup_information
+ puts_time "Restoring #{definition.task.human_name} ... ".color(:blue)
- unless task.enabled
+ unless definition.task.enabled
puts_time "[DISABLED]".color(:cyan)
return
end
- task.restore
+ warning = definition.task.pre_restore_warning
+ if warning.present?
+ puts_time warning.color(:red)
+ Gitlab::TaskHelpers.ask_to_continue
+ end
+
+ definition.task.restore(File.join(Gitlab.config.backup.path, definition.destination_path))
+
puts_time "done".color(:green)
+
+ warning = definition.task.post_restore_warning
+ if warning.present?
+ puts_time warning.color(:red)
+ Gitlab::TaskHelpers.ask_to_continue
+ end
+
+ rescue Gitlab::TaskAbortedByUserError
+ puts_time "Quitting...".color(:red)
+ exit 1
end
- def write_info
+ private
+
+ def read_backup_information
+ @backup_information ||= YAML.load_file(File.join(backup_path, MANIFEST_NAME))
+ end
+
+ def write_backup_information
# Make sure there is a connection
ActiveRecord::Base.connection.reconnect!
Dir.chdir(backup_path) do
- File.open("#{backup_path}/backup_information.yml", "w+") do |file|
+ File.open("#{backup_path}/#{MANIFEST_NAME}", "w+") do |file|
file << backup_information.to_yaml.gsub(/^---\n/, '')
end
end
end
+ def build_backup_information
+ @backup_information ||= {
+ db_version: ActiveRecord::Migrator.current_version.to_s,
+ backup_created_at: Time.now,
+ gitlab_version: Gitlab::VERSION,
+ tar_version: tar_version,
+ installation_type: Gitlab::INSTALLATION_TYPE,
+ skipped: ENV["SKIP"]
+ }
+ end
+
+ def backup_information
+ raise Backup::Error, "#{MANIFEST_NAME} not yet loaded" unless @backup_information
+
+ @backup_information
+ end
+
def pack
Dir.chdir(backup_path) do
# create archive
@@ -182,8 +235,11 @@ module Backup
upload = directory.files.create(create_attributes)
if upload
- progress.puts "done".color(:green)
- upload
+ if upload.respond_to?(:encryption) && upload.encryption
+ progress.puts "done (encrypted with #{upload.encryption})".color(:green)
+ else
+ progress.puts "done".color(:green)
+ end
else
puts "uploading backup to #{remote_directory} failed".color(:red)
raise Backup::Error, 'Backup failed'
@@ -193,18 +249,19 @@ module Backup
def cleanup
progress.print "Deleting tmp directories ... "
- backup_contents.each do |dir|
- next unless File.exist?(File.join(backup_path, dir))
-
- if FileUtils.rm_rf(File.join(backup_path, dir))
- progress.puts "done".color(:green)
- else
- puts "deleting tmp directory '#{dir}' failed".color(:red)
- raise Backup::Error, 'Backup failed'
- end
+ remove_backup_path(MANIFEST_NAME)
+ @definitions.each do |_, definition|
+ remove_backup_path(definition.cleanup_path || definition.destination_path)
end
end
+ def remove_backup_path(path)
+ return unless File.exist?(File.join(backup_path, path))
+
+ FileUtils.rm_rf(File.join(backup_path, path))
+ progress.puts "done".color(:green)
+ end
+
def remove_tmp
# delete tmp inside backups
progress.print "Deleting backups/tmp ... "
@@ -255,15 +312,15 @@ module Backup
def verify_backup_version
Dir.chdir(backup_path) do
# restoring mismatching backups can lead to unexpected problems
- if settings[:gitlab_version] != Gitlab::VERSION
+ if backup_information[:gitlab_version] != Gitlab::VERSION
progress.puts(<<~HEREDOC.color(:red))
GitLab version mismatch:
Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!
Please switch to the following version and try again:
- version: #{settings[:gitlab_version]}
+ version: #{backup_information[:gitlab_version]}
HEREDOC
progress.puts
- progress.puts "Hint: git checkout v#{settings[:gitlab_version]}"
+ progress.puts "Hint: git checkout v#{backup_information[:gitlab_version]}"
exit 1
end
end
@@ -319,13 +376,11 @@ module Backup
end
def skipped?(item)
- settings[:skipped] && settings[:skipped].include?(item) || !enabled_task?(item)
+ backup_information[:skipped] && backup_information[:skipped].include?(item)
end
- private
-
def enabled_task?(task_name)
- @tasks[task_name].enabled
+ @definitions[task_name].task.enabled
end
def backup_file?(file)
@@ -333,7 +388,7 @@ module Backup
end
def non_tarred_backup?
- File.exist?(File.join(backup_path, 'backup_information.yml'))
+ File.exist?(File.join(backup_path, MANIFEST_NAME))
end
def backup_path
@@ -380,19 +435,10 @@ module Backup
end
def backup_contents
- folders_to_backup + archives_to_backup + ["backup_information.yml"]
- end
-
- def archives_to_backup
- ARCHIVES_TO_BACKUP.map { |name| (name + ".tar.gz") unless skipped?(name) }.compact
- end
-
- def folders_to_backup
- FOLDERS_TO_BACKUP.select { |name| !skipped?(name) && Dir.exist?(File.join(backup_path, name)) }
- end
-
- def settings
- @settings ||= YAML.load_file("backup_information.yml")
+ [MANIFEST_NAME] + @definitions.reject do |name, definition|
+ skipped?(name) || !enabled_task?(name) ||
+ (definition.destination_optional && !File.exist?(File.join(backup_path, definition.destination_path)))
+ end.values.map(&:destination_path)
end
def tar_file
@@ -403,17 +449,6 @@ module Backup
end
end
- def backup_information
- @backup_information ||= {
- db_version: ActiveRecord::Migrator.current_version.to_s,
- backup_created_at: Time.now,
- gitlab_version: Gitlab::VERSION,
- tar_version: tar_version,
- installation_type: Gitlab::INSTALLATION_TYPE,
- skipped: ENV["SKIP"]
- }
- end
-
def create_attributes
attrs = {
key: remote_target,
@@ -447,11 +482,11 @@ module Backup
Gitlab.config.backup.upload.connection&.provider&.downcase == 'google'
end
- def repository_backup_strategy
+ def repository_backup_strategy(incremental)
if Feature.enabled?(:gitaly_backup, default_enabled: :yaml)
max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence
max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence
- Backup::GitalyBackup.new(progress, max_parallelism: max_concurrency, storage_parallelism: max_storage_concurrency)
+ Backup::GitalyBackup.new(progress, incremental: incremental, max_parallelism: max_concurrency, storage_parallelism: max_storage_concurrency)
else
Backup::GitalyRpcBackup.new(progress)
end