backup_rake_spec.rb 12.2 KB
Newer Older
Hugo Duksis's avatar
Hugo Duksis committed
1 2 3 4
require 'spec_helper'
require 'rake'

describe 'gitlab:app namespace rake task' do
5 6
  let(:enable_registry) { true }

Hugo Duksis's avatar
Hugo Duksis committed
7
  before :all do
8
    Rake.application.rake_require 'tasks/gitlab/helpers'
9 10 11
    Rake.application.rake_require 'tasks/gitlab/backup'
    Rake.application.rake_require 'tasks/gitlab/shell'
    Rake.application.rake_require 'tasks/gitlab/db'
12
    Rake.application.rake_require 'tasks/cache'
13

Hugo Duksis's avatar
Hugo Duksis committed
14 15
    # empty task as env is already loaded
    Rake::Task.define_task :environment
16 17 18

    # We need this directory to run `gitlab:backup:create` task
    FileUtils.mkdir_p('public/uploads')
Hugo Duksis's avatar
Hugo Duksis committed
19 20
  end

21 22 23 24
  before do
    stub_container_registry_config(enabled: enable_registry)
  end

25 26 27 28 29
  def run_rake_task(task_name)
    Rake::Task[task_name].reenable
    Rake.application.invoke_task task_name
  end

30
  def reenable_backup_sub_tasks
31
    %w{db repo uploads builds artifacts lfs registry}.each do |subtask|
32 33 34 35
      Rake::Task["gitlab:backup:#{subtask}:create"].reenable
    end
  end

Hugo Duksis's avatar
Hugo Duksis committed
36 37 38
  describe 'backup_restore' do
    before do
      # avoid writing task output to spec progress
39
      allow($stdout).to receive :write
Hugo Duksis's avatar
Hugo Duksis committed
40 41 42 43
    end

    context 'gitlab version' do
      before do
44
        allow(Dir).to receive(:glob).and_return(['1_gitlab_backup.tar'])
45
        allow(Dir).to receive(:chdir)
46
        allow(File).to receive(:exist?).and_return(true)
47 48 49 50 51
        allow(Kernel).to receive(:system).and_return(true)
        allow(FileUtils).to receive(:cp_r).and_return(true)
        allow(FileUtils).to receive(:mv).and_return(true)
        allow(Rake::Task["gitlab:shell:setup"]).
          to receive(:invoke).and_return(true)
52
        ENV['force'] = 'yes'
Hugo Duksis's avatar
Hugo Duksis committed
53 54
      end

55
      let(:gitlab_version) { Gitlab::VERSION }
Hugo Duksis's avatar
Hugo Duksis committed
56

57
      it 'fails on mismatch' do
58
        allow(YAML).to receive(:load_file).
59
          and_return({ gitlab_version: "not #{gitlab_version}" })
60 61 62

        expect { run_rake_task('gitlab:backup:restore') }.
          to raise_error(SystemExit)
Hugo Duksis's avatar
Hugo Duksis committed
63 64
      end

65
      it 'invokes restoration on match' do
66
        allow(YAML).to receive(:load_file).
67
          and_return({ gitlab_version: gitlab_version })
68 69 70 71 72 73 74
        expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke)
        expect(Rake::Task['gitlab:backup:db:restore']).to receive(:invoke)
        expect(Rake::Task['gitlab:backup:repo:restore']).to receive(:invoke)
        expect(Rake::Task['gitlab:backup:builds:restore']).to receive(:invoke)
        expect(Rake::Task['gitlab:backup:uploads:restore']).to receive(:invoke)
        expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive(:invoke)
        expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke)
75
        expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke)
76
        expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke)
77
        expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
Hugo Duksis's avatar
Hugo Duksis committed
78 79 80
      end
    end
  end # backup_restore task
81

82
  describe 'backup' do
83 84 85 86
    def tars_glob
      Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar'))
    end

87
    def create_backup
88
      FileUtils.rm tars_glob
89 90 91 92

      # Redirect STDOUT and run the rake task
      orig_stdout = $stdout
      $stdout = StringIO.new
93
      reenable_backup_sub_tasks
94
      run_rake_task('gitlab:backup:create')
95
      reenable_backup_sub_tasks
96 97
      $stdout = orig_stdout

98
      @backup_tar = tars_glob.first
99 100
    end

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 165 166 167 168 169 170 171 172
    def restore_backup
      orig_stdout = $stdout
      $stdout = StringIO.new
      reenable_backup_sub_tasks
      run_rake_task('gitlab:backup:restore')
      reenable_backup_sub_tasks
      $stdout = orig_stdout
    end

    describe 'backup creation and deletion using annex and custom_hooks' do
      let(:project) { create(:project) }
      let(:user_backup_path) { "repositories/#{project.path_with_namespace}" }

      before(:each) do
        @origin_cd = Dir.pwd

        path = File.join(project.repository.path_to_repo, filename)
        FileUtils.mkdir_p(path)
        FileUtils.touch(File.join(path, "dummy.txt"))

        # We need to use the full path instead of the relative one
        allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(File.expand_path(Gitlab.config.gitlab_shell.path, Rails.root.to_s))

        ENV["SKIP"] = "db"
        create_backup
      end

      after(:each) do
        ENV["SKIP"] = ""
        FileUtils.rm(@backup_tar)
        Dir.chdir(@origin_cd)
      end

      context 'project uses git-annex and successfully creates backup' do
        let(:filename) { "annex" }

        it 'creates annex.tar and project bundle' do
          tar_contents, exit_status = Gitlab::Popen.popen(%W{tar -tvf #{@backup_tar}})

          expect(exit_status).to eq(0)
          expect(tar_contents).to match(user_backup_path)
          expect(tar_contents).to match("#{user_backup_path}/annex.tar")
          expect(tar_contents).to match("#{user_backup_path}.bundle")
        end

        it 'restores files correctly' do
          restore_backup

          expect(Dir.entries(File.join(project.repository.path, "annex"))).to include("dummy.txt")
        end
      end

      context 'project uses custom_hooks and successfully creates backup' do
        let(:filename) { "custom_hooks" }

        it 'creates custom_hooks.tar and project bundle' do
          tar_contents, exit_status = Gitlab::Popen.popen(%W{tar -tvf #{@backup_tar}})

          expect(exit_status).to eq(0)
          expect(tar_contents).to match(user_backup_path)
          expect(tar_contents).to match("#{user_backup_path}/custom_hooks.tar")
          expect(tar_contents).to match("#{user_backup_path}.bundle")
        end

        it 'restores files correctly' do
          restore_backup

          expect(Dir.entries(File.join(project.repository.path, "custom_hooks"))).to include("dummy.txt")
        end
      end
    end

173 174 175 176
    context 'tar creation' do
      before do
        create_backup
      end
177

178 179
      after do
        FileUtils.rm(@backup_tar)
180 181
      end

182
      context 'archive file permissions' do
183
        it 'sets correct permissions on the tar file' do
184 185
          expect(File.exist?(@backup_tar)).to be_truthy
          expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600')
186 187
        end

188 189 190 191 192 193 194 195 196 197 198 199
        context 'with custom archive_permissions' do
          before do
            allow(Gitlab.config.backup).to receive(:archive_permissions).and_return(0651)
            # We created a backup in a before(:all) so it got the default permissions.
            # We now need to do some work to create a _new_ backup file using our stub.
            FileUtils.rm(@backup_tar)
            create_backup
          end

          it 'uses the custom permissions' do
            expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100651')
          end
200 201
        end
      end
202

203
      it 'sets correct permissions on the tar contents' do
204 205 206 207 208 209 210 211 212 213 214 215 216
        tar_contents, exit_status = Gitlab::Popen.popen(
          %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz}
        )
        expect(exit_status).to eq(0)
        expect(tar_contents).to match('db/')
        expect(tar_contents).to match('uploads.tar.gz')
        expect(tar_contents).to match('repositories/')
        expect(tar_contents).to match('builds.tar.gz')
        expect(tar_contents).to match('artifacts.tar.gz')
        expect(tar_contents).to match('lfs.tar.gz')
        expect(tar_contents).to match('registry.tar.gz')
        expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/)
      end
217

218
      it 'deletes temp directories' do
219 220 221 222 223 224
        temp_dirs = Dir.glob(
          File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs,registry}')
        )

        expect(temp_dirs).to be_empty
      end
225

226 227 228
      context 'registry disabled' do
        let(:enable_registry) { false }

229
        it 'does not create registry.tar.gz' do
230 231 232 233 234 235 236
          tar_contents, exit_status = Gitlab::Popen.popen(
            %W{tar -tvf #{@backup_tar}}
          )
          expect(exit_status).to eq(0)
          expect(tar_contents).not_to match('registry.tar.gz')
        end
      end
237
    end
238

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
    context 'multiple repository storages' do
      let(:project_a) { create(:project, repository_storage: 'default') }
      let(:project_b) { create(:project, repository_storage: 'custom') }

      before do
        FileUtils.mkdir('tmp/tests/default_storage')
        FileUtils.mkdir('tmp/tests/custom_storage')
        storages = {
          'default' => 'tmp/tests/default_storage',
          'custom' => 'tmp/tests/custom_storage'
        }
        allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)

        # Create the projects now, after mocking the settings but before doing the backup
        project_a
        project_b

        # We only need a backup of the repositories for this test
        ENV["SKIP"] = "db,uploads,builds,artifacts,lfs,registry"
        create_backup
      end

      after do
        FileUtils.rm_rf('tmp/tests/default_storage')
        FileUtils.rm_rf('tmp/tests/custom_storage')
        FileUtils.rm(@backup_tar)
      end
266

267
      it 'includes repositories in all repository storages' do
268
        tar_contents, exit_status = Gitlab::Popen.popen(
269
          %W{tar -tvf #{@backup_tar} repositories}
270 271
        )
        expect(exit_status).to eq(0)
272 273
        expect(tar_contents).to match("repositories/#{project_a.path_with_namespace}.bundle")
        expect(tar_contents).to match("repositories/#{project_b.path_with_namespace}.bundle")
274 275
      end
    end
276
  end # backup_create task
277 278 279 280 281 282 283 284 285

  describe "Skipping items" do
    def tars_glob
      Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar'))
    end

    before :all do
      @origin_cd = Dir.pwd

286
      reenable_backup_sub_tasks
287

288
      FileUtils.rm tars_glob
289 290 291 292

      # Redirect STDOUT and run the rake task
      orig_stdout = $stdout
      $stdout = StringIO.new
293
      ENV["SKIP"] = "repositories,uploads"
294 295 296
      run_rake_task('gitlab:backup:create')
      $stdout = orig_stdout

297
      @backup_tar = tars_glob.first
298 299 300 301 302 303 304 305
    end

    after :all do
      FileUtils.rm(@backup_tar)
      Dir.chdir @origin_cd
    end

    it "does not contain skipped item" do
306
      tar_contents, _exit_status = Gitlab::Popen.popen(
307
        %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz}
308 309 310
      )

      expect(tar_contents).to match('db/')
311 312
      expect(tar_contents).to match('uploads.tar.gz')
      expect(tar_contents).to match('builds.tar.gz')
313
      expect(tar_contents).to match('artifacts.tar.gz')
Marin Jankovski's avatar
Marin Jankovski committed
314
      expect(tar_contents).to match('lfs.tar.gz')
315
      expect(tar_contents).to match('registry.tar.gz')
316 317 318 319
      expect(tar_contents).not_to match('repositories/')
    end

    it 'does not invoke repositories restore' do
320
      allow(Rake::Task['gitlab:shell:setup']).
321
        to receive(:invoke).and_return(true)
322 323
      allow($stdout).to receive :write

324 325 326 327 328 329 330
      expect(Rake::Task['gitlab:db:drop_tables']).to receive :invoke
      expect(Rake::Task['gitlab:backup:db:restore']).to receive :invoke
      expect(Rake::Task['gitlab:backup:repo:restore']).not_to receive :invoke
      expect(Rake::Task['gitlab:backup:uploads:restore']).not_to receive :invoke
      expect(Rake::Task['gitlab:backup:builds:restore']).to receive :invoke
      expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive :invoke
      expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke
331
      expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke
332
      expect(Rake::Task['gitlab:shell:setup']).to receive :invoke
333
      expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
334 335
    end
  end
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366

  describe "Human Readable Backup Name" do
    def tars_glob
      Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar'))
    end

    before :all do
      @origin_cd = Dir.pwd

      reenable_backup_sub_tasks

      FileUtils.rm tars_glob

      # Redirect STDOUT and run the rake task
      orig_stdout = $stdout
      $stdout = StringIO.new
      run_rake_task('gitlab:backup:create')
      $stdout = orig_stdout

      @backup_tar = tars_glob.first
    end

    after :all do
      FileUtils.rm(@backup_tar)
      Dir.chdir @origin_cd
    end

    it 'name has human readable time' do
      expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_gitlab_backup.tar$/)
    end
  end
Hugo Duksis's avatar
Hugo Duksis committed
367
end # gitlab:app namespace