summaryrefslogtreecommitdiff
path: root/lib/gitlab/background_migration/migrate_legacy_uploads.rb
blob: af1ad930aedd2867b77e4901ff217eb71ea0723a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# frozen_string_literal: true

module Gitlab
  module BackgroundMigration
    # This migration takes all legacy uploads (that were uploaded using AttachmentUploader)
    # and migrate them to the new (FileUploader) location (=under projects).
    #
    # We have dependencies (uploaders) in this migration because extracting code would add a lot of complexity
    # and possible errors could appear as the logic in the uploaders is not trivial.
    #
    # This migration will be removed in 12.4 in order to get rid of a migration that depends on
    # the application code.
    class MigrateLegacyUploads
      include Database::MigrationHelpers
      include ::Gitlab::Utils::StrongMemoize

      # This class takes a legacy upload and migrates it to the correct location
      class UploadMover
        include Gitlab::Utils::StrongMemoize

        attr_reader :upload, :project, :note

        def initialize(upload)
          @upload = upload
          @note = Note.find_by(id: upload.model_id)
          @project = note&.project
        end

        def execute
          return unless upload

          if !project
            # if we don't have models associated with the upload we can not move it
            say "MigrateLegacyUploads: Deleting upload due to model not found: #{upload.inspect}"
            destroy_legacy_upload
          elsif note.is_a?(LegacyDiffNote)
            handle_legacy_note_upload
          elsif !legacy_file_exists?
            # if we can not find the file we just remove the upload record
            say "MigrateLegacyUploads: Deleting upload due to file not found: #{upload.inspect}"
            destroy_legacy_upload
          else
            migrate_upload
          end
        end

        private

        def migrate_upload
          return unless copy_upload_to_project

          add_upload_link_to_note_text
          destroy_legacy_file
          destroy_legacy_upload
        end

        # we should proceed and log whenever one upload copy fails, no matter the reasons
        # rubocop: disable Lint/RescueException
        def copy_upload_to_project
          @uploader = FileUploader.copy_to(legacy_file_uploader, project)

          say "MigrateLegacyUploads: Copied file #{legacy_file_uploader.file.path} -> #{@uploader.file.path}"
          true
        rescue Exception => e
          say "MigrateLegacyUploads: File #{legacy_file_uploader.file.path} couldn't be copied to project uploads. Error: #{e.message}"
          false
        end
        # rubocop: enable Lint/RescueException

        def destroy_legacy_upload
          note.remove_attachment = true
          note.save

          if upload.destroy
            say "MigrateLegacyUploads: Upload #{upload.inspect} was destroyed."
          else
            say "MigrateLegacyUploads: Upload #{upload.inspect} destroy failed."
          end
        end

        def destroy_legacy_file
          legacy_file_uploader.file.delete
        end

        def add_upload_link_to_note_text
          new_text = "#{note.note} \n #{@uploader.markdown_link}"
          note.update!(
            note: new_text
          )
        end

        def legacy_file_uploader
          strong_memoize(:legacy_file_uploader) do
            uploader = upload.build_uploader
            uploader.retrieve_from_store!(File.basename(upload.path))
            uploader
          end
        end

        def legacy_file_exists?
          legacy_file_uploader.file.exists?
        end

        def handle_legacy_note_upload
          note.note += "\n \n Attachment ##{upload.id} with URL \"#{note.attachment.url}\" failed to migrate \
               for model class #{note.class}. See #{help_doc_link}."
          note.save

          say "MigrateLegacyUploads: LegacyDiffNote ##{note.id} found, can't move the file: #{upload.inspect} for upload ##{upload.id}. See #{help_doc_link}."
        end

        def say(message)
          Rails.logger.info(message)
        end

        def help_doc_link
          'https://docs.gitlab.com/ee/administration/troubleshooting/migrations.html#legacy-upload-migration'
        end
      end

      def perform(start_id, end_id)
        Upload.where(id: start_id..end_id, uploader: 'AttachmentUploader').find_each do |upload|
          UploadMover.new(upload).execute
        end
      end
    end
  end
end