Read about our upcoming Code of Conduct on this issue

legacy_namespace.rb 4.02 KB
Newer Older
1
2
# frozen_string_literal: true

3
4
5
6
module Storage
  module LegacyNamespace
    extend ActiveSupport::Concern

7
8
    include Gitlab::ShellAdapter

9
    def move_dir
10
11
12
13
      proj_with_tags = first_project_with_container_registry_tags

      if proj_with_tags
        raise Gitlab::UpdatePathError.new("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
15
      end

Jasper Maes's avatar
Jasper Maes committed
16
17
      parent_was = if parent_changed? && parent_id_before_last_save.present?
                     Namespace.find(parent_id_before_last_save) # raise NotFound early if needed
Michael Kozono's avatar
Michael Kozono committed
18
19
                   end

Michael Kozono's avatar
Michael Kozono committed
20
      move_repositories
21

22
      if parent_changed?
Michael Kozono's avatar
Michael Kozono committed
23
        former_parent_full_path = parent_was&.full_path
24
25
26
27
28
29
30
        parent_full_path = parent&.full_path
        Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
        Gitlab::PagesTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
      else
        Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
        Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
      end
31
32
33
34
35
36
37

      # If repositories moved successfully we need to
      # send update instructions to users.
      # However we cannot allow rollback since we moved namespace dir
      # So we basically we mute exceptions in next actions
      begin
        send_update_instructions
38
        write_projects_repository_config
39
40
41
      rescue => e
        # Raise if development/test environment, else just notify Sentry
        Gitlab::Sentry.track_exception(e, extra: { full_path_was: full_path_was, full_path: full_path, action: 'move_dir' })
42
      end
43
44

      true # false would cancel later callbacks but not rollback
45
46
47
48
    end

    # Hooks

49
    # Save the storages before the projects are destroyed to use them on after destroy
50
    def prepare_for_destroy
51
      old_repository_storages
52
53
54
55
    end

    private

Michael Kozono's avatar
Michael Kozono committed
56
    def move_repositories
57
58
      # Move the namespace directory in all storages used by member projects
      repository_storages.each do |repository_storage|
Michael Kozono's avatar
Michael Kozono committed
59
        # Ensure old directory exists before moving it
60
        gitlab_shell.add_namespace(repository_storage, full_path_was)
Michael Kozono's avatar
Michael Kozono committed
61
62

        # Ensure new directory exists before moving it (if there's a parent)
63
        gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
Michael Kozono's avatar
Michael Kozono committed
64

65
        unless gitlab_shell.mv_namespace(repository_storage, full_path_was, full_path)
Michael Kozono's avatar
Michael Kozono committed
66

67
          Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_was} to #{full_path}"
Michael Kozono's avatar
Michael Kozono committed
68
69
70
71
72
73
74
75

          # if we cannot move namespace directory we should rollback
          # db changes in order to prevent out of sync between db and fs
          raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
        end
      end
    end

76
77
    def old_repository_storages
      @old_repository_storage_paths ||= repository_storages
78
79
    end

80
    def repository_storages
81
82
83
84
      # We need to get the storage paths for all the projects, even the ones that are
      # pending delete. Unscoping also get rids of the default order, which causes
      # problems with SELECT DISTINCT.
      Project.unscoped do
85
        all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage)
86
87
88
89
90
      end
    end

    def rm_dir
      # Remove the namespace directory in all storages paths used by member projects
91
      old_repository_storages.each do |repository_storage|
92
93
94
95
        # Move namespace directory into trash.
        # We will remove it later async
        new_path = "#{full_path}+#{id}+deleted"

96
        if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
97
          Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
98

99
          # Remove namespace directory async with delay so
100
101
          # GitLab has time to remove all projects first
          run_after_commit do
102
            GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
103
104
105
106
107
108
          end
        end
      end
    end
  end
end