2020-04-24 12:10:16 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module ImportExport
|
|
|
|
module Group
|
|
|
|
class TreeRestorer
|
|
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
|
2021-01-28 15:09:06 +00:00
|
|
|
attr_reader :user, :shared, :groups_mapping
|
2020-04-24 12:10:16 +00:00
|
|
|
|
|
|
|
def initialize(user:, shared:, group:)
|
|
|
|
@user = user
|
|
|
|
@shared = shared
|
|
|
|
@top_level_group = group
|
|
|
|
@groups_mapping = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
def restore
|
|
|
|
group_ids = relation_reader.consume_relation('groups', '_all').map { |value, _idx| Integer(value) }
|
|
|
|
root_group_id = group_ids.delete_at(0)
|
|
|
|
|
|
|
|
process_root(root_group_id)
|
|
|
|
|
|
|
|
group_ids.each do |group_id|
|
|
|
|
process_child(group_id)
|
|
|
|
end
|
|
|
|
|
|
|
|
true
|
2021-04-26 12:09:44 +00:00
|
|
|
rescue StandardError => e
|
2020-04-24 12:10:16 +00:00
|
|
|
shared.error(e)
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
class GroupAttributes
|
|
|
|
attr_reader :attributes, :group_id, :id, :path
|
|
|
|
|
|
|
|
def initialize(group_id, relation_reader)
|
|
|
|
@group_id = group_id
|
|
|
|
|
|
|
|
@path = "groups/#{group_id}"
|
|
|
|
@attributes = relation_reader.consume_attributes(@path)
|
|
|
|
@id = @attributes.delete('id')
|
|
|
|
|
|
|
|
unless @id == @group_id
|
|
|
|
raise ArgumentError, "Invalid group_id for #{group_id}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_attribute(name)
|
|
|
|
attributes.delete(name)
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_attributes(*names)
|
|
|
|
names.map(&method(:delete_attribute))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
private_constant :GroupAttributes
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def process_root(group_id)
|
|
|
|
group_attributes = GroupAttributes.new(group_id, relation_reader)
|
|
|
|
|
|
|
|
# name and path are not imported on the root group to avoid conflict
|
|
|
|
# with existing groups name and/or path.
|
|
|
|
group_attributes.delete_attributes('name', 'path')
|
|
|
|
|
|
|
|
restore_group(@top_level_group, group_attributes)
|
|
|
|
end
|
|
|
|
|
|
|
|
def process_child(group_id)
|
|
|
|
group_attributes = GroupAttributes.new(group_id, relation_reader)
|
|
|
|
|
|
|
|
group = create_group(group_attributes)
|
|
|
|
|
|
|
|
restore_group(group, group_attributes)
|
2021-04-26 12:09:44 +00:00
|
|
|
rescue StandardError => e
|
2020-12-11 21:10:13 +00:00
|
|
|
import_failure_service.log_import_failure(
|
|
|
|
source: 'process_child',
|
|
|
|
relation_key: 'group',
|
|
|
|
exception: e
|
|
|
|
)
|
2020-04-24 12:10:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def create_group(group_attributes)
|
|
|
|
parent_id = group_attributes.delete_attribute('parent_id')
|
|
|
|
name = group_attributes.delete_attribute('name')
|
|
|
|
path = group_attributes.delete_attribute('path')
|
|
|
|
|
|
|
|
parent_group = @groups_mapping.fetch(parent_id) { raise(ArgumentError, 'Parent group not found') }
|
|
|
|
|
2020-12-11 21:10:13 +00:00
|
|
|
group = ::Groups::CreateService.new(
|
2020-04-24 12:10:16 +00:00
|
|
|
user,
|
|
|
|
name: name,
|
|
|
|
path: path,
|
|
|
|
parent_id: parent_group.id,
|
|
|
|
visibility_level: sub_group_visibility_level(group_attributes.attributes, parent_group)
|
|
|
|
).execute
|
2020-12-11 21:10:13 +00:00
|
|
|
|
|
|
|
group.validate!
|
|
|
|
|
|
|
|
group
|
2020-04-24 12:10:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def restore_group(group, group_attributes)
|
|
|
|
@groups_mapping[group_attributes.id] = group
|
|
|
|
|
|
|
|
Group::GroupRestorer.new(
|
|
|
|
user: user,
|
|
|
|
shared: shared,
|
|
|
|
group: group,
|
|
|
|
attributes: group_attributes.attributes,
|
|
|
|
importable_path: group_attributes.path,
|
|
|
|
relation_reader: relation_reader,
|
|
|
|
reader: reader
|
|
|
|
).restore
|
|
|
|
end
|
|
|
|
|
|
|
|
def relation_reader
|
|
|
|
strong_memoize(:relation_reader) do
|
|
|
|
ImportExport::JSON::NdjsonReader.new(
|
|
|
|
File.join(shared.export_path, 'tree')
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def sub_group_visibility_level(group_hash, parent_group)
|
|
|
|
original_visibility_level = group_hash['visibility_level'] || Gitlab::VisibilityLevel::PRIVATE
|
|
|
|
|
|
|
|
if parent_group && parent_group.visibility_level < original_visibility_level
|
|
|
|
Gitlab::VisibilityLevel.closest_allowed_level(parent_group.visibility_level)
|
|
|
|
else
|
|
|
|
original_visibility_level
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def reader
|
|
|
|
strong_memoize(:reader) do
|
|
|
|
Gitlab::ImportExport::Reader.new(
|
|
|
|
shared: @shared,
|
|
|
|
config: Gitlab::ImportExport::Config.new(
|
|
|
|
config: Gitlab::ImportExport.group_config_file
|
|
|
|
).to_h
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
2020-12-11 21:10:13 +00:00
|
|
|
|
|
|
|
def import_failure_service
|
|
|
|
Gitlab::ImportExport::ImportFailureService.new(@top_level_group)
|
|
|
|
end
|
2020-04-24 12:10:16 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|