# frozen_string_literal: true module Gitlab module ImportExport module Group class TreeRestorer include Gitlab::Utils::StrongMemoize attr_reader :user, :shared, :groups_mapping 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 rescue StandardError => e 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) rescue StandardError => e import_failure_service.log_import_failure( source: 'process_child', relation_key: 'group', exception: e ) 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') } group = ::Groups::CreateService.new( user, name: name, path: path, parent_id: parent_group.id, visibility_level: sub_group_visibility_level(group_attributes.attributes, parent_group) ).execute group.validate! group 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 def import_failure_service Gitlab::ImportExport::ImportFailureService.new(@top_level_group) end end end end end