2020-09-21 20:09:59 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'yaml'
|
|
|
|
|
|
|
|
module Backup
|
2022-05-26 20:08:34 -04:00
|
|
|
# Backup and restores repositories by querying the database
|
2022-03-14 23:08:45 -04:00
|
|
|
class Repositories < Task
|
|
|
|
extend ::Gitlab::Utils::Override
|
|
|
|
|
2022-05-26 20:08:34 -04:00
|
|
|
# @param [IO] progress IO interface to output progress
|
|
|
|
# @param [Object] :strategy Fetches backups from gitaly
|
|
|
|
# @param [Array<String>] :storages Filter by specified storage names. Empty means all storages.
|
|
|
|
# @param [Array<String>] :paths Filter by specified project paths. Empty means all projects, groups and snippets.
|
|
|
|
def initialize(progress, strategy:, storages: [], paths: [])
|
2022-03-14 23:08:45 -04:00
|
|
|
super(progress)
|
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
@strategy = strategy
|
2022-05-16 08:07:51 -04:00
|
|
|
@storages = storages
|
2022-05-26 20:08:34 -04:00
|
|
|
@paths = paths
|
2020-09-21 20:09:59 -04:00
|
|
|
end
|
|
|
|
|
2022-03-14 23:08:45 -04:00
|
|
|
override :dump
|
2022-05-26 20:08:34 -04:00
|
|
|
def dump(destination_path, backup_id)
|
|
|
|
strategy.start(:create, destination_path, backup_id: backup_id)
|
2022-03-24 05:07:33 -04:00
|
|
|
enqueue_consecutive
|
2020-09-21 20:09:59 -04:00
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
ensure
|
2022-01-11 01:10:58 -05:00
|
|
|
strategy.finish!
|
2020-09-21 20:09:59 -04:00
|
|
|
end
|
|
|
|
|
2022-03-14 23:08:45 -04:00
|
|
|
override :restore
|
2022-05-26 20:08:34 -04:00
|
|
|
def restore(destination_path)
|
|
|
|
strategy.start(:restore, destination_path)
|
2021-05-20 02:10:43 -04:00
|
|
|
enqueue_consecutive
|
|
|
|
|
|
|
|
ensure
|
2022-01-11 01:10:58 -05:00
|
|
|
strategy.finish!
|
2021-01-14 16:10:37 -05:00
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
cleanup_snippets_without_repositories
|
2021-01-14 16:10:37 -05:00
|
|
|
restore_object_pools
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2022-05-26 20:08:34 -04:00
|
|
|
attr_reader :strategy, :storages, :paths
|
2021-09-14 11:12:05 -04:00
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
def enqueue_consecutive
|
|
|
|
enqueue_consecutive_projects
|
|
|
|
enqueue_consecutive_snippets
|
2020-09-21 20:09:59 -04:00
|
|
|
end
|
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
def enqueue_consecutive_projects
|
2020-10-06 11:08:33 -04:00
|
|
|
project_relation.find_each(batch_size: 1000) do |project|
|
2021-05-20 02:10:43 -04:00
|
|
|
enqueue_project(project)
|
2020-09-21 20:09:59 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
def enqueue_consecutive_snippets
|
2022-05-16 08:07:51 -04:00
|
|
|
snippet_relation.find_each(batch_size: 1000) { |snippet| enqueue_snippet(snippet) }
|
2020-10-06 11:08:33 -04:00
|
|
|
end
|
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
def enqueue_project(project)
|
|
|
|
strategy.enqueue(project, Gitlab::GlRepository::PROJECT)
|
|
|
|
strategy.enqueue(project, Gitlab::GlRepository::WIKI)
|
|
|
|
strategy.enqueue(project, Gitlab::GlRepository::DESIGN)
|
2020-09-21 20:09:59 -04:00
|
|
|
end
|
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
def enqueue_snippet(snippet)
|
|
|
|
strategy.enqueue(snippet, Gitlab::GlRepository::SNIPPET)
|
2020-10-06 11:08:33 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def project_relation
|
2022-05-16 08:07:51 -04:00
|
|
|
scope = Project.includes(:route, :group, namespace: :owner)
|
|
|
|
scope = scope.id_in(ProjectRepository.for_repository_storage(storages).select(:project_id)) if storages.any?
|
2022-06-07 08:08:05 -04:00
|
|
|
if paths.any?
|
|
|
|
scope = scope.where_full_path_in(paths).or(
|
|
|
|
Project.where(namespace_id: Namespace.where_full_path_in(paths).self_and_descendants)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2022-05-16 08:07:51 -04:00
|
|
|
scope
|
|
|
|
end
|
|
|
|
|
|
|
|
def snippet_relation
|
|
|
|
scope = Snippet.all
|
|
|
|
scope = scope.id_in(SnippetRepository.for_repository_storage(storages).select(:snippet_id)) if storages.any?
|
2022-06-07 08:08:05 -04:00
|
|
|
if paths.any?
|
|
|
|
scope = scope.joins(:project).merge(
|
|
|
|
Project.where_full_path_in(paths).or(
|
|
|
|
Project.where(namespace_id: Namespace.where_full_path_in(paths).self_and_descendants)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2022-05-16 08:07:51 -04:00
|
|
|
scope
|
2020-10-06 11:08:33 -04:00
|
|
|
end
|
|
|
|
|
2020-09-21 20:09:59 -04:00
|
|
|
def restore_object_pools
|
|
|
|
PoolRepository.includes(:source_project).find_each do |pool|
|
|
|
|
progress.puts " - Object pool #{pool.disk_path}..."
|
|
|
|
|
2021-02-17 13:09:19 -05:00
|
|
|
unless pool.source_project
|
|
|
|
progress.puts " - Object pool #{pool.disk_path}... " + "[SKIPPED]".color(:cyan)
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2020-09-21 20:09:59 -04:00
|
|
|
pool.state = 'none'
|
|
|
|
pool.save
|
|
|
|
|
|
|
|
pool.schedule
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-10-13 11:08:53 -04:00
|
|
|
# Snippets without a repository should be removed because they failed to import
|
|
|
|
# due to having invalid repositories
|
2021-05-20 02:10:43 -04:00
|
|
|
def cleanup_snippets_without_repositories
|
|
|
|
invalid_snippets = []
|
2020-10-13 11:08:53 -04:00
|
|
|
|
2022-05-16 08:07:51 -04:00
|
|
|
snippet_relation.find_each(batch_size: 1000).each do |snippet|
|
2021-05-20 02:10:43 -04:00
|
|
|
response = Snippets::RepositoryValidationService.new(nil, snippet).execute
|
|
|
|
next if response.success?
|
2020-09-21 20:09:59 -04:00
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
snippet.repository.remove
|
|
|
|
progress.puts("Snippet #{snippet.full_path} can't be restored: #{response.message}")
|
2020-09-21 20:09:59 -04:00
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
invalid_snippets << snippet.id
|
2020-09-21 20:09:59 -04:00
|
|
|
end
|
|
|
|
|
2021-05-20 02:10:43 -04:00
|
|
|
Snippet.id_in(invalid_snippets).delete_all
|
2020-09-21 20:09:59 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-01-14 16:10:37 -05:00
|
|
|
|
2021-05-11 17:10:21 -04:00
|
|
|
Backup::Repositories.prepend_mod_with('Backup::Repositories')
|