1# frozen_string_literal: true 2 3module Storage 4 module LegacyNamespace 5 extend ActiveSupport::Concern 6 7 include Gitlab::ShellAdapter 8 9 def move_dir 10 proj_with_tags = first_project_with_container_registry_tags 11 12 if proj_with_tags 13 raise Gitlab::UpdatePathError, "Namespace #{name} (#{id}) cannot be moved because at least one project (e.g. #{proj_with_tags.name} (#{proj_with_tags.id})) has tags in container registry" 14 end 15 16 parent_was = if saved_change_to_parent? && parent_id_before_last_save.present? 17 Namespace.find(parent_id_before_last_save) # raise NotFound early if needed 18 end 19 20 move_repositories 21 22 if saved_change_to_parent? 23 former_parent_full_path = parent_was&.full_path 24 parent_full_path = parent&.full_path 25 Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path) 26 27 if any_project_with_pages_deployed? 28 run_after_commit do 29 Gitlab::PagesTransfer.new.async.move_namespace(path, former_parent_full_path, parent_full_path) 30 end 31 end 32 else 33 Gitlab::UploadsTransfer.new.rename_namespace(full_path_before_last_save, full_path) 34 35 if any_project_with_pages_deployed? 36 full_path_was = full_path_before_last_save 37 38 run_after_commit do 39 Gitlab::PagesTransfer.new.async.rename_namespace(full_path_was, full_path) 40 end 41 end 42 end 43 44 # If repositories moved successfully we need to 45 # send update instructions to users. 46 # However we cannot allow rollback since we moved namespace dir 47 # So we basically we mute exceptions in next actions 48 begin 49 send_update_instructions 50 write_projects_repository_config 51 rescue StandardError => e 52 Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, 53 full_path_before_last_save: full_path_before_last_save, 54 full_path: full_path, 55 action: 'move_dir') 56 end 57 58 true # false would cancel later callbacks but not rollback 59 end 60 61 # Hooks 62 63 # Save the storages before the projects are destroyed to use them on after destroy 64 def prepare_for_destroy 65 old_repository_storages 66 end 67 68 private 69 70 def move_repositories 71 # Move the namespace directory in all storages used by member projects 72 repository_storages(legacy_only: true).each do |repository_storage| 73 # Ensure old directory exists before moving it 74 Gitlab::GitalyClient::NamespaceService.allow do 75 gitlab_shell.add_namespace(repository_storage, full_path_before_last_save) 76 77 # Ensure new directory exists before moving it (if there's a parent) 78 gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent 79 80 unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path) 81 82 Gitlab::AppLogger.error("Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}") 83 84 # if we cannot move namespace directory we should rollback 85 # db changes in order to prevent out of sync between db and fs 86 raise Gitlab::UpdatePathError, 'namespace directory cannot be moved' 87 end 88 end 89 end 90 end 91 92 def old_repository_storages 93 @old_repository_storage_paths ||= repository_storages(legacy_only: true) 94 end 95 96 def repository_storages(legacy_only: false) 97 # We need to get the storage paths for all the projects, even the ones that are 98 # pending delete. Unscoping also get rids of the default order, which causes 99 # problems with SELECT DISTINCT. 100 Project.unscoped do 101 namespace_projects = all_projects 102 namespace_projects = namespace_projects.without_storage_feature(:repository) if legacy_only 103 namespace_projects.pluck(Arel.sql('distinct(repository_storage)')) 104 end 105 end 106 107 def rm_dir 108 # Remove the namespace directory in all storages paths used by member projects 109 old_repository_storages.each do |repository_storage| 110 # Move namespace directory into trash. 111 # We will remove it later async 112 new_path = "#{full_path}+#{id}+deleted" 113 114 Gitlab::GitalyClient::NamespaceService.allow do 115 if gitlab_shell.mv_namespace(repository_storage, full_path, new_path) 116 Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}") 117 118 # Remove namespace directory async with delay so 119 # GitLab has time to remove all projects first 120 run_after_commit do 121 GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path) 122 end 123 end 124 end 125 end 126 end 127 end 128end 129