summaryrefslogtreecommitdiff
path: root/lib/backup/gitaly_rpc_backup.rb
blob: 89ed27cfa13b922527a3f66b61520887c0eff19e (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
# frozen_string_literal: true

module Backup
  # Backup and restores repositories using the gitaly RPC
  class GitalyRpcBackup
    def initialize(progress)
      @progress = progress
    end

    def start(type, backup_repos_path)
      raise Error, 'already started' if @type

      @type = type
      @backup_repos_path = backup_repos_path
      case type
      when :create
        FileUtils.rm_rf(backup_repos_path)
        FileUtils.mkdir_p(Gitlab.config.backup.path)
        FileUtils.mkdir(backup_repos_path, mode: 0700)
      when :restore
        # no op
      else
        raise Error, "unknown backup type: #{type}"
      end
    end

    def finish!
      @type = nil
    end

    def enqueue(container, repository_type)
      backup_restore = BackupRestore.new(
        progress,
        repository_type.repository_for(container),
        @backup_repos_path
      )

      case @type
      when :create
        backup_restore.backup
      when :restore
        backup_restore.restore(always_create: repository_type.project?)
      else
        raise Error, 'not started'
      end
    end

    def parallel_enqueue?
      true
    end

    private

    attr_reader :progress

    class BackupRestore
      attr_accessor :progress, :repository, :backup_repos_path

      def initialize(progress, repository, backup_repos_path)
        @progress = progress
        @repository = repository
        @backup_repos_path = backup_repos_path
      end

      def backup
        progress.puts " * #{display_repo_path} ... "

        if repository.empty?
          progress.puts " * #{display_repo_path} ... " + "[EMPTY] [SKIPPED]".color(:cyan)
          return
        end

        FileUtils.mkdir_p(repository_backup_path)

        repository.bundle_to_disk(path_to_bundle)
        repository.gitaly_repository_client.backup_custom_hooks(custom_hooks_tar)

        progress.puts " * #{display_repo_path} ... " + "[DONE]".color(:green)

      rescue StandardError => e
        progress.puts "[Failed] backing up #{display_repo_path}".color(:red)
        progress.puts "Error #{e}".color(:red)
      end

      def restore(always_create: false)
        progress.puts " * #{display_repo_path} ... "

        repository.remove rescue nil

        if File.exist?(path_to_bundle)
          repository.create_from_bundle(path_to_bundle)
          restore_custom_hooks
        elsif always_create
          repository.create_repository
        end

        progress.puts " * #{display_repo_path} ... " + "[DONE]".color(:green)

      rescue StandardError => e
        progress.puts "[Failed] restoring #{display_repo_path}".color(:red)
        progress.puts "Error #{e}".color(:red)
      end

      private

      def display_repo_path
        "#{repository.full_path} (#{repository.disk_path})"
      end

      def repository_backup_path
        @repository_backup_path ||= File.join(backup_repos_path, repository.disk_path)
      end

      def path_to_bundle
        @path_to_bundle ||= File.join(backup_repos_path, repository.disk_path + '.bundle')
      end

      def restore_custom_hooks
        return unless File.exist?(custom_hooks_tar)

        repository.gitaly_repository_client.restore_custom_hooks(custom_hooks_tar)
      end

      def custom_hooks_tar
        File.join(repository_backup_path, "custom_hooks.tar")
      end
    end
  end
end