summaryrefslogtreecommitdiff
path: root/app/services/projects/import_export/export_service.rb
blob: ddbcfbb675c7d44f0aca4644172225dcc1c914c9 (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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# frozen_string_literal: true

module Projects
  module ImportExport
    class ExportService < BaseService
      prepend Measurable

      def initialize(*args)
        super

        @shared = project.import_export_shared
        @logger = Gitlab::Export::Logger.build
      end

      def execute(after_export_strategy = nil)
        unless project.template_source? || can?(current_user, :admin_project, project)
          raise ::Gitlab::ImportExport::Error.permission_error(current_user, project)
        end

        save_all!
        execute_after_export_action(after_export_strategy)
      ensure
        cleanup
      end

      def exporters
        [
          version_saver, avatar_saver, project_tree_saver, uploads_saver,
          repo_saver, wiki_repo_saver, lfs_saver, snippets_repo_saver, design_repo_saver
        ]
      end

      protected

      def extra_attributes_for_measurement
        {
          current_user: current_user&.name,
          project_full_path: project&.full_path,
          file_path: shared.export_path
        }
      end

      private

      attr_accessor :shared
      attr_reader :logger

      def execute_after_export_action(after_export_strategy)
        return unless after_export_strategy

        unless after_export_strategy.execute(current_user, project)
          notify_error
        end
      end

      def save_all!
        log_info('Project export started')

        if save_exporters && save_export_archive
          log_info('Project successfully exported')
        else
          notify_error!
        end
      end

      def save_exporters
        exporters.all? do |exporter|
          log_info("#{exporter.class.name} saver started")

          exporter.save
        end
      end

      def save_export_archive
        @export_saver ||= Gitlab::ImportExport::Saver.save(exportable: project, shared: shared)
      end

      def version_saver
        @version_saver ||= Gitlab::ImportExport::VersionSaver.new(shared: shared)
      end

      def avatar_saver
        @avatar_saver ||= Gitlab::ImportExport::AvatarSaver.new(project: project, shared: shared)
      end

      def project_tree_saver
        @project_tree_saver ||= tree_saver_class.new(
          project: project,
          current_user: current_user,
          shared: shared,
          params: params,
          logger: logger)
      end

      def tree_saver_class
        Gitlab::ImportExport::Project::TreeSaver
      end

      def uploads_saver
        @uploads_saver ||= Gitlab::ImportExport::UploadsSaver.new(project: project, shared: shared)
      end

      def repo_saver
        @repo_saver ||= Gitlab::ImportExport::RepoSaver.new(exportable: project, shared: shared)
      end

      def wiki_repo_saver
        @wiki_repo_saver ||= Gitlab::ImportExport::WikiRepoSaver.new(exportable: project, shared: shared)
      end

      def lfs_saver
        @lfs_saver ||= Gitlab::ImportExport::LfsSaver.new(project: project, shared: shared)
      end

      def snippets_repo_saver
        @snippets_repo_saver ||= Gitlab::ImportExport::SnippetsRepoSaver.new(
          current_user: current_user,
          project: project,
          shared: shared
        )
      end

      def design_repo_saver
        @design_repo_saver ||= Gitlab::ImportExport::DesignRepoSaver.new(exportable: project, shared: shared)
      end

      def cleanup
        FileUtils.rm_rf(shared.archive_path) if shared&.archive_path
      end

      def notify_error!
        notify_error

        raise Gitlab::ImportExport::Error, shared.errors.to_sentence
      end

      def log_info(message)
        logger.info(
          message: message,
          **log_base_data
        )
      end

      def notify_error
        logger.error(
          message: 'Project export error',
          export_errors: shared.errors.join(', '),
          **log_base_data
        )

        user = current_user
        errors = shared.errors

        project.run_after_commit_or_now do |project|
          NotificationService.new.project_not_exported(project, user, errors)
        end
      end

      def log_base_data
        @log_base_data ||= Gitlab::ImportExport::LogUtil.exportable_to_log_payload(project)
      end
    end
  end
end