From 4aa8f00e1f07b3e0c24dca9c95b43166af3517fe Mon Sep 17 00:00:00 2001
From: Aakriti Gupta <agupta@gitlab.com>
Date: Thu, 31 Oct 2024 01:56:51 +0000
Subject: [PATCH] Migrate and refactor files and repo targets to unified
 backups

---
 .../lib/gitlab/backup/cli.rb                  |   1 +
 .../lib/gitlab/backup/cli/backup_executor.rb  |  15 +-
 .../backup/cli/context/source_context.rb      |  14 +-
 .../lib/gitlab/backup/cli/errors.rb           |   2 +
 .../backup/cli/errors/file_restore_error.rb   |  22 ++
 .../backup/cli/errors/gitaly_backup_error.rb  |  22 ++
 .../lib/gitlab/backup/cli/repo_type.rb        |  14 ++
 .../lib/gitlab/backup/cli/restore_executor.rb |  23 +-
 .../lib/gitlab/backup/cli/targets.rb          |   4 +
 .../lib/gitlab/backup/cli/targets/database.rb |  14 +-
 .../lib/gitlab/backup/cli/targets/files.rb    | 103 +++++++++
 .../backup/cli/targets/gitaly_backup.rb       | 187 ++++++++++++++++
 .../backup/cli/targets/gitaly_client.rb       |  42 ++++
 .../cli/targets/object_storage/google.rb      |  10 +-
 .../gitlab/backup/cli/targets/repositories.rb |  96 +++++++++
 .../lib/gitlab/backup/cli/targets/target.rb   |  15 +-
 .../lib/gitlab/backup/cli/tasks/artifacts.rb  |   2 +-
 .../lib/gitlab/backup/cli/tasks/builds.rb     |   2 +-
 .../backup/cli/tasks/ci_secure_files.rb       |   2 +-
 .../lib/gitlab/backup/cli/tasks/database.rb   |   2 +-
 .../lib/gitlab/backup/cli/tasks/lfs.rb        |   2 +-
 .../lib/gitlab/backup/cli/tasks/packages.rb   |   2 +-
 .../lib/gitlab/backup/cli/tasks/pages.rb      |   2 +-
 .../lib/gitlab/backup/cli/tasks/registry.rb   |  14 +-
 .../gitlab/backup/cli/tasks/repositories.rb   |  19 +-
 .../lib/gitlab/backup/cli/tasks/task.rb       |  27 +--
 .../backup/cli/tasks/terraform_state.rb       |   2 +-
 .../lib/gitlab/backup/cli/tasks/uploads.rb    |   2 +-
 .../lib/gitlab/backup/cli/utils/tar.rb        |  13 ++
 .../spec/fixtures/{ => config}/gitlab.yml     |   0
 .../gitlab/backup/cli/gitlab_config_spec.rb   |   2 +-
 .../gitlab/backup/cli/targets/files_spec.rb   | 188 +++++++++++++++++
 .../backup/cli/targets/gitaly_backup_spec.rb  | 199 ++++++++++++++++++
 .../cli/targets/object_storage/google_spec.rb |  16 +-
 .../backup/cli/targets/repositories_spec.rb   |  74 +++++++
 .../spec/gitlab/backup/cli/tasks/task_spec.rb |  11 +-
 .../spec/gitlab/backup/cli/utils/tar_spec.rb  |  53 ++++-
 gems/gitlab-backup-cli/spec/spec_helper.rb    |   1 +
 .../context_shared_examples.rb                |  22 +-
 shared/artifacts/tmp/cache/.gitkeep           |   0
 shared/artifacts/tmp/uploads/.gitkeep         |   0
 41 files changed, 1122 insertions(+), 119 deletions(-)
 create mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/file_restore_error.rb
 create mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/gitaly_backup_error.rb
 create mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/repo_type.rb
 create mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/files.rb
 create mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/gitaly_backup.rb
 create mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/gitaly_client.rb
 create mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/repositories.rb
 rename gems/gitlab-backup-cli/spec/fixtures/{ => config}/gitlab.yml (100%)
 create mode 100644 gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/files_spec.rb
 create mode 100644 gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/gitaly_backup_spec.rb
 create mode 100644 gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/repositories_spec.rb
 delete mode 100644 shared/artifacts/tmp/cache/.gitkeep
 delete mode 100644 shared/artifacts/tmp/uploads/.gitkeep

diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli.rb
index b8fa1e08fc88e..ee6fef06a67d8 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli.rb
@@ -20,6 +20,7 @@ module Cli
       autoload :GitlabConfig, 'gitlab/backup/cli/gitlab_config'
       autoload :Metadata, 'gitlab/backup/cli/metadata'
       autoload :Output, 'gitlab/backup/cli/output'
+      autoload :RepoType, 'gitlab/backup/cli/repo_type'
       autoload :RestoreExecutor, 'gitlab/backup/cli/restore_executor'
       autoload :Runner, 'gitlab/backup/cli/runner'
       autoload :Shell, 'gitlab/backup/cli/shell'
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/backup_executor.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/backup_executor.rb
index 8df1a3edc89da..4d27bdfc231ca 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/backup_executor.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/backup_executor.rb
@@ -51,20 +51,17 @@ def build_metadata
         end
 
         def execute_all_tasks
-          # TODO: when we migrate targets to the new codebase, recreate options to have only what we need here
-          # https://gitlab.com/gitlab-org/gitlab/-/issues/454906
-          options = ::Backup::Options.new(
-            remote_directory: backup_bucket,
-            container_registry_bucket: registry_bucket,
-            service_account_file: service_account_file
-          )
           tasks = []
 
-          Gitlab::Backup::Cli::Tasks.build_each(context: context, options: options) do |task|
+          Gitlab::Backup::Cli::Tasks.build_each(context: context) do |task|
+            # This is a temporary hack while we move away from options and use config instead
+            # This hack will be removed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/498455
+            task.set_registry_bucket(registry_bucket) if task.is_a?(Gitlab::Backup::Cli::Tasks::Registry)
+
             Gitlab::Backup::Cli::Output.info("Executing Backup of #{task.human_name}...")
 
             duration = measure_duration do
-              task.backup!(workdir, metadata.backup_id)
+              task.backup!(workdir)
               tasks << task
             end
 
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/context/source_context.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/context/source_context.rb
index 1feb2efbd80f2..ba3a2caa285cc 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/context/source_context.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/context/source_context.rb
@@ -104,7 +104,7 @@ def upload_path
           end
 
           def config(object_type)
-            Gitlab.config[object_type]
+            gitlab_config[object_type]
           end
 
           def env
@@ -112,6 +112,18 @@ def env
               ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence || "development")
           end
 
+          def config_repositories_storages
+            gitlab_config.dig(env, 'repositories', 'storages')
+          end
+
+          def gitaly_backup_path
+            gitlab_config.dig(env, 'backup', 'gitaly_backup_path')
+          end
+
+          def gitaly_token
+            gitlab_config.dig(env, 'gitaly', 'token')
+          end
+
           private
 
           # Return the shared path used as a fallback base location to each blob type
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors.rb
index 68060d7dcb144..6cd73adf3a201 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors.rb
@@ -6,6 +6,8 @@ module Cli
       module Errors
         autoload :DatabaseBackupError, 'gitlab/backup/cli/errors/database_backup_error'
         autoload :FileBackupError, 'gitlab/backup/cli/errors/file_backup_error'
+        autoload :FileRestoreError, 'gitlab/backup/cli/errors/file_restore_error'
+        autoload :GitalyBackupError, 'gitlab/backup/cli/errors/gitaly_backup_error'
       end
     end
   end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/file_restore_error.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/file_restore_error.rb
new file mode 100644
index 0000000000000..451948fe52baf
--- /dev/null
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/file_restore_error.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Backup
+    module Cli
+      module Errors
+        class FileRestoreError < StandardError
+          attr_reader :error_message
+
+          def initialize(error_message:)
+            super
+            @error_message = error_message
+          end
+
+          def message
+            "Restore operation failed: #{error_message}"
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/gitaly_backup_error.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/gitaly_backup_error.rb
new file mode 100644
index 0000000000000..318515d934a5d
--- /dev/null
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/gitaly_backup_error.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Backup
+    module Cli
+      module Errors
+        class GitalyBackupError < StandardError
+          attr_reader :error_message
+
+          def initialize(error_message = '')
+            super
+            @error_message = error_message
+          end
+
+          def message
+            "Repository Backup/Restore failed. #{error_message}"
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/repo_type.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/repo_type.rb
new file mode 100644
index 0000000000000..15e9cae753945
--- /dev/null
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/repo_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Backup
+    module Cli
+      class RepoType
+        PROJECT = :project
+        WIKI    = :wiki
+        SNIPPET = :snippet
+        DESIGN  = :design
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/restore_executor.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/restore_executor.rb
index 25a76382d5ea0..e7cc30ad34378 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/restore_executor.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/restore_executor.rb
@@ -41,10 +41,6 @@ def execute
           execute_all_tasks
         end
 
-        def backup_options
-          @backup_options ||= build_backup_options!
-        end
-
         def metadata
           @metadata ||= read_metadata!
         end
@@ -57,14 +53,16 @@ def release!
         private
 
         def execute_all_tasks
-          # TODO: when we migrate targets to the new codebase, recreate options to have only what we need here
-          # https://gitlab.com/gitlab-org/gitlab/-/issues/454906
           tasks = []
-          Gitlab::Backup::Cli::Tasks.build_each(context: context, options: backup_options) do |task|
+          Gitlab::Backup::Cli::Tasks.build_each(context: context) do |task|
+            # This is a temporary hack while we move away from options and use config instead
+            # This hack will be removed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/498455
+            task.set_registry_bucket(registry_bucket) if task.is_a?(Gitlab::Backup::Cli::Tasks::Registry)
+
             Gitlab::Backup::Cli::Output.info("Executing restoration of #{task.human_name}...")
 
             duration = measure_duration do
-              tasks << { name: task.human_name, result: task.restore!(archive_directory, backup_id) }
+              tasks << { name: task.human_name, result: task.restore!(archive_directory) }
             end
 
             next if task.object_storage?
@@ -87,15 +85,6 @@ def read_metadata!
           @metadata = Gitlab::Backup::Cli::Metadata::BackupMetadata.load!(archive_directory)
         end
 
-        def build_backup_options!
-          ::Backup::Options.new(
-            backup_id: backup_id,
-            remote_directory: backup_bucket,
-            container_registry_bucket: registry_bucket,
-            service_account_file: service_account_file
-          )
-        end
-
         # @return [Pathname] temporary directory
         def create_temporary_workdir!
           # Ensure base directory exists
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets.rb
index e66a3d78ce553..8a6679276769a 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets.rb
@@ -6,7 +6,11 @@ module Cli
       module Targets
         autoload :Target, 'gitlab/backup/cli/targets/target'
         autoload :Database, 'gitlab/backup/cli/targets/database'
+        autoload :Files, 'gitlab/backup/cli/targets/files'
         autoload :ObjectStorage, 'gitlab/backup/cli/targets/object_storage'
+        autoload :GitalyBackup, 'gitlab/backup/cli/targets/gitaly_backup'
+        autoload :GitalyClient, 'gitlab/backup/cli/targets/gitaly_client'
+        autoload :Repositories, 'gitlab/backup/cli/targets/repositories'
       end
     end
   end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/database.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/database.rb
index 5cf484ad92ab4..ea5dc7757821d 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/database.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/database.rb
@@ -17,14 +17,16 @@ class Database < Target
           ].freeze
           IGNORED_ERRORS_REGEXP = Regexp.union(IGNORED_ERRORS).freeze
 
-          def initialize(options:)
-            super(options: options)
-
+          def initialize
             @errors = []
-            @force = options.force?
+
+            # This flag will be removed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/494209
+            # This option will be reintroduced as part of
+            # https://gitlab.com/gitlab-org/gitlab/-/issues/498453
+            @force = false
           end
 
-          def dump(destination_dir, _)
+          def dump(destination_dir)
             FileUtils.mkdir_p(destination_dir)
 
             each_database(destination_dir) do |backup_connection|
@@ -74,7 +76,7 @@ def dump(destination_dir, _)
             end
           end
 
-          def restore(destination_dir, _)
+          def restore(destination_dir)
             @errors = []
 
             base_models_for_backup.each do |database_name, _|
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/files.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/files.rb
new file mode 100644
index 0000000000000..7a6c18b4017b6
--- /dev/null
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/files.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Backup
+    module Cli
+      module Targets
+        class Files < Target
+          DEFAULT_EXCLUDE = ['lost+found'].freeze
+
+          attr_reader :excludes
+
+          # @param [String] storage_path
+          # @param [Array] excludes
+          def initialize(context, storage_path, excludes: [])
+            super(context)
+
+            @storage_path = storage_path
+            @excludes = excludes
+          end
+
+          def dump(destination)
+            archive_file = [destination, 'w', 0o600]
+            tar_command = Utils::Tar.new.pack_from_stdin_cmd(
+              target_directory: storage_realpath,
+              target: '.',
+              excludes: excludes)
+
+            compression_cmd = Utils::Compression.compression_command
+
+            pipeline = Shell::Pipeline.new(tar_command, compression_cmd)
+
+            result = pipeline.run!(output: archive_file)
+
+            return if success?(result)
+
+            raise Errors::FileBackupError.new(storage_realpath, destination)
+          end
+
+          def restore(source)
+            # Existing files will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/499876
+            if File.exist?(storage_realpath)
+              Output.warning "Ignoring existing files at #{storage_realpath} and continuing restore."
+            end
+
+            archive_file = source.to_s
+            tar_command = Utils::Tar.new.extract_from_stdin_cmd(target_directory: storage_realpath)
+
+            decompression_cmd = Utils::Compression.decompression_command
+
+            pipeline = Shell::Pipeline.new(decompression_cmd, tar_command)
+            result = pipeline.run!(input: archive_file)
+
+            return if success?(result)
+
+            raise Errors::FileRestoreError.new(error_message: result.stderr)
+          end
+
+          private
+
+          def success?(result)
+            return true if result.success?
+
+            return true if ignore_non_success?(
+              result.status_list[1].exitstatus,
+              result.stderr
+            )
+
+            false
+          end
+
+          def noncritical_warning_matcher
+            /^g?tar: \.: Cannot mkdir: No such file or directory$/
+          end
+
+          def ignore_non_success?(exitstatus, output)
+            # tar can exit with nonzero code:
+            #  1 - if some files changed (i.e. a CI job is currently writes to log)
+            #  2 - if it cannot create `.` directory (see issue https://gitlab.com/gitlab-org/gitlab/-/issues/22442)
+            #  http://www.gnu.org/software/tar/manual/html_section/tar_19.html#Synopsis
+            #  so check tar status 1 or stderr output against some non-critical warnings
+            if exitstatus == 1
+              Output.print_info "Ignoring tar exit status 1 'Some files differ': #{output}"
+              return true
+            end
+
+            # allow tar to fail with other non-success status if output contain non-critical warning
+            if noncritical_warning_matcher&.match?(output)
+              Output.print_info(
+                "Ignoring non-success exit status #{exitstatus} due to output of non-critical warning(s): #{output}")
+              return true
+            end
+
+            false
+          end
+
+          def storage_realpath
+            @storage_realpath ||= File.realpath(@storage_path)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/gitaly_backup.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/gitaly_backup.rb
new file mode 100644
index 0000000000000..c3770e6d78a3e
--- /dev/null
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/gitaly_backup.rb
@@ -0,0 +1,187 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Backup
+    module Cli
+      module Targets
+        class GitalyBackup
+          # Backup and restores repositories using gitaly-backup
+          #
+          # gitaly-backup can work in parallel and accepts a list of repositories
+          # through input pipe using a specific json format for both backup and restore
+          attr_reader :context
+
+          def initialize(context)
+            @context = context
+          end
+
+          def start(type, backup_repos_path, backup_id: nil, remove_all_repositories: nil)
+            raise Gitlab::Backup::Cli::Errors::GitalyBackupError, 'already started' if started?
+
+            FileUtils.rm_rf(backup_repos_path) if type == :create
+
+            @input_stream, stdout, @thread = Open3.popen2(
+              build_env,
+              bin_path,
+              *gitaly_backup_args(type, backup_repos_path.to_s, backup_id, remove_all_repositories)
+            )
+
+            @out_reader = Thread.new do
+              IO.copy_stream(stdout, $stdout)
+            end
+          end
+
+          def finish!
+            return unless started?
+
+            @input_stream.close
+            @thread.join
+            status =  @thread.value
+
+            @thread = nil
+
+            return unless status.exitstatus != 0
+
+            raise Gitlab::Backup::Cli::Errors::GitalyBackupError,
+              "gitaly-backup exit status #{status.exitstatus}"
+          end
+
+          def enqueue(container, repo_type)
+            raise Gitlab::Backup::Cli::Errors::GitalyBackupError, 'not started' unless started?
+            raise Gitlab::Backup::Cli::Errors::GitalyBackupError, 'no container for repo type' unless container
+
+            storage, relative_path, gl_project_path, always_create = repository_info_for(container, repo_type)
+
+            schedule_backup_job(storage, relative_path, gl_project_path, always_create)
+          end
+
+          private
+
+          def repository_info_for(container, repo_type)
+            case repo_type
+            when RepoType::PROJECT
+              [container.repository_storage,
+                container.disk_path || container.full_path,
+                container.full_path,
+                true]
+            when RepoType::WIKI
+              wiki_repo_info(container)
+            when RepoType::SNIPPET
+              [container.repository_storage,
+                container.disk_path || container.full_path,
+                container.full_path,
+                false]
+            when RepoType::DESIGN
+              [design_repo_storage(container),
+                container.project.disk_path,
+                container.project.full_path,
+                false]
+            end
+          end
+
+          def design_repo_storage(container)
+            return container.repository.repository_storage if container.repository.respond_to?(:repository_storage)
+
+            container.repository_storage
+          end
+
+          def wiki_repo_info(container)
+            wiki = container.respond_to?(:wiki) ? container.wiki : container
+            [wiki.repository_storage,
+              wiki.disk_path || wiki.full_path,
+              wiki.full_path,
+              false]
+          end
+
+          def gitaly_backup_args(type, backup_repos_path, backup_id, remove_all_repositories)
+            command = case type
+                      when :create
+                        'create'
+                      when :restore
+                        'restore'
+                      else
+                        raise Gitlab::Backup::Cli::Errors::GitalyBackupError, "unknown backup type: #{type}"
+                      end
+
+            args = [command] + ['-path', backup_repos_path, '-layout', 'manifest']
+
+            case type
+            when :create
+              args += ['-id', backup_id] if backup_id
+            when :restore
+              args += ['-remove-all-repositories', remove_all_repositories.join(',')] if remove_all_repositories
+              args += ['-id', backup_id] if backup_id
+            end
+
+            args
+          end
+
+          # Schedule a new backup job through a non-blocking JSON based pipe protocol
+          #
+          # @see https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/gitaly-backup.md
+          def schedule_backup_job(storage, relative_path, gl_project_path, always_create)
+            json_job = {
+              storage_name: storage,
+              relative_path: relative_path,
+              gl_project_path: gl_project_path,
+              always_create: always_create
+            }.to_json
+
+            @input_stream.puts(json_job)
+          end
+
+          def gitaly_servers
+            storages = context.config_repositories_storages
+            unless storages.keys
+              raise Gitlab::Backup::Cli::Errors::GitalyBackupError,
+                "No repositories' storages found."
+            end
+
+            storages.keys.index_with do |storage_name|
+              GitalyClient.new(storages, context.gitaly_token).connection_data(storage_name)
+            end
+          end
+
+          def gitaly_servers_encoded
+            Base64.strict_encode64(JSON.dump(gitaly_servers))
+          end
+
+          # These variables will be moved to a config file via
+          # https://gitlab.com/gitlab-org/gitlab/-/issues/500437
+          def default_cert_dir
+            ENV.fetch('SSL_CERT_DIR', OpenSSL::X509::DEFAULT_CERT_DIR)
+          end
+
+          def default_cert_file
+            ENV.fetch('SSL_CERT_FILE', OpenSSL::X509::DEFAULT_CERT_FILE)
+          end
+
+          def build_env
+            {
+              'SSL_CERT_FILE' => default_cert_file,
+              'SSL_CERT_DIR' => default_cert_dir,
+              'GITALY_SERVERS' => gitaly_servers_encoded
+            }.merge(current_env)
+          end
+
+          def current_env
+            ENV
+          end
+
+          def started?
+            @thread.present?
+          end
+
+          def bin_path
+            unless context.gitaly_backup_path.present?
+              raise Gitlab::Backup::Cli::Errors::GitalyBackupError,
+                'gitaly-backup binary not found and gitaly_backup_path is not configured'
+            end
+
+            File.absolute_path(context.gitaly_backup_path)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/gitaly_client.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/gitaly_client.rb
new file mode 100644
index 0000000000000..5385348386ba2
--- /dev/null
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/gitaly_client.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Backup
+    module Cli
+      module Targets
+        class GitalyClient
+          attr_reader :storages, :gitaly_token
+
+          def initialize(storages, gitaly_token)
+            @storages = storages
+            @gitaly_token = gitaly_token
+          end
+
+          def connection_data(storage)
+            raise "storage not found: #{storage.inspect}" if storages[storage].nil?
+
+            { 'address' => address(storage), 'token' => token(storage) }
+          end
+
+          private
+
+          def address(storage)
+            address = storages[storage]['gitaly_address']
+            raise "storage #{storage.inspect} is missing a gitaly_address" unless address.present?
+
+            unless %w[tcp unix tls dns].include?(URI(address).scheme)
+              raise "Unsupported Gitaly address: " \
+                    "#{address.inspect} does not use URL scheme 'tcp' or 'unix' or 'tls' or 'dns'"
+            end
+
+            address
+          end
+
+          def token(storage)
+            storages[storage]['gitaly_token'].presence || gitaly_token
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage/google.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage/google.rb
index 7900106285736..3a823b72df8dc 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage/google.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage/google.rb
@@ -12,14 +12,15 @@ class Google < Target
 
             attr_accessor :object_type, :backup_bucket, :client, :config, :results
 
-            def initialize(object_type, options, config)
+            def initialize(object_type, remote_directory, config)
               @object_type = object_type
-              @backup_bucket = options.remote_directory
+              @backup_bucket = remote_directory
               @config = config
               @client = ::Google::Cloud::StorageTransfer.storage_transfer_service
             end
 
-            def dump(_, backup_id)
+            # @param [String] backup_id unique identifier for the backup
+            def dump(backup_id)
               response = find_or_create_job(backup_id, "backup")
               run_request = {
                 project_id: backup_job_spec(backup_id)[:project_id],
@@ -28,7 +29,8 @@ def dump(_, backup_id)
               @results = client.run_transfer_job run_request
             end
 
-            def restore(_, backup_id)
+            # @param [String] backup_id unique identifier for the backup
+            def restore(backup_id)
               response = find_or_create_job(backup_id, "restore")
               run_request = {
                 project_id: restore_job_spec(backup_id)[:project_id],
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/repositories.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/repositories.rb
new file mode 100644
index 0000000000000..9cb1b430f80bf
--- /dev/null
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/repositories.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'yaml'
+
+module Gitlab
+  module Backup
+    module Cli
+      module Targets
+        # Backup and restores repositories by querying the database
+        class Repositories < Target
+          def dump(destination)
+            strategy.start(:create, destination)
+            enqueue_consecutive
+
+          ensure
+            strategy.finish!
+          end
+
+          def restore(source)
+            strategy.start(:restore,
+              source,
+              remove_all_repositories: remove_all_repositories)
+            enqueue_consecutive
+
+          ensure
+            strategy.finish!
+
+            restore_object_pools
+          end
+
+          def strategy
+            @strategy ||= GitalyBackup.new(context)
+          end
+
+          private
+
+          def remove_all_repositories
+            context.config_repositories_storages.keys
+          end
+
+          def enqueue_consecutive
+            enqueue_consecutive_projects
+            enqueue_consecutive_snippets
+          end
+
+          def enqueue_consecutive_projects
+            project_relation.find_each(batch_size: 1000) do |project|
+              enqueue_project(project)
+            end
+          end
+
+          def enqueue_consecutive_snippets
+            snippet_relation.find_each(batch_size: 1000) { |snippet| enqueue_snippet(snippet) }
+          end
+
+          def enqueue_project(project)
+            strategy.enqueue(project, Gitlab::Backup::Cli::RepoType::PROJECT)
+            strategy.enqueue(project, Gitlab::Backup::Cli::RepoType::WIKI)
+
+            return unless project.design_management_repository
+
+            strategy.enqueue(project.design_management_repository, Gitlab::Backup::Cli::RepoType::DESIGN)
+          end
+
+          def enqueue_snippet(snippet)
+            strategy.enqueue(snippet, Gitlab::Backup::Cli::RepoType::SNIPPET)
+          end
+
+          def project_relation
+            Project.includes(:route, :group, :namespace)
+          end
+
+          def snippet_relation
+            Snippet.all
+          end
+
+          def restore_object_pools
+            PoolRepository.includes(:source_project).find_each do |pool|
+              Output.info " - Object pool #{pool.disk_path}..."
+
+              unless pool.source_project
+                Output.info " - Object pool #{pool.disk_path}... [SKIPPED]"
+                next
+              end
+
+              pool.state = 'none'
+              pool.save
+
+              pool.schedule
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/target.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/target.rb
index d775702642384..b272552a4e391 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/target.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/target.rb
@@ -6,14 +6,10 @@ module Cli
       module Targets
         # Abstract class used to implement a Backup Target
         class Target
-          # Backup creation and restore option flags
-          #
-          # TODO: Migrate to a unified backup specific Options implementation
-          # @return [::Backup::Options]
-          attr_reader :options
+          attr_reader :context
 
-          def initialize(options:)
-            @options = options
+          def initialize(context = nil)
+            @context = context
           end
 
           def asynchronous?
@@ -23,13 +19,12 @@ def asynchronous?
           # dump task backup to `path`
           #
           # @param [String] path fully qualified backup task destination
-          # @param [String] backup_id unique identifier for the backup
-          def dump(path, backup_id)
+          def dump(path)
             raise NotImplementedError
           end
 
           # restore task backup from `path`
-          def restore(path, backup_id)
+          def restore(path)
             raise NotImplementedError
           end
         end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/artifacts.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/artifacts.rb
index 14ed04f567c5e..9af8ceaaa8844 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/artifacts.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/artifacts.rb
@@ -14,7 +14,7 @@ def destination_path = 'artifacts.tar.gz'
           private
 
           def local
-            ::Backup::Targets::Files.new(nil, storage_path, options: options, excludes: ['tmp'])
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path, excludes: ['tmp'])
           end
 
           def storage_path = context.ci_job_artifacts_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/builds.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/builds.rb
index b94f384880719..ed5dffe5071f1 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/builds.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/builds.rb
@@ -14,7 +14,7 @@ def destination_path = 'builds.tar.gz'
           private
 
           def target
-            ::Backup::Targets::Files.new(nil, storage_path, options: options)
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path)
           end
 
           def storage_path = context.ci_builds_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/ci_secure_files.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/ci_secure_files.rb
index f819d3fd3ab24..55a8f84343db9 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/ci_secure_files.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/ci_secure_files.rb
@@ -14,7 +14,7 @@ def destination_path = 'ci_secure_files.tar.gz'
           private
 
           def local
-            ::Backup::Targets::Files.new(nil, storage_path, options: options, excludes: ['tmp'])
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path, excludes: ['tmp'])
           end
 
           def storage_path = context.ci_secure_files_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/database.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/database.rb
index 1790b109c03f7..5c4e94326c9f0 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/database.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/database.rb
@@ -16,7 +16,7 @@ def cleanup_path = 'db'
           private
 
           def target
-            ::Gitlab::Backup::Cli::Targets::Database.new(options: options)
+            ::Gitlab::Backup::Cli::Targets::Database.new
           end
         end
       end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/lfs.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/lfs.rb
index 46c96d4c8ecc6..50cb98eadbe73 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/lfs.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/lfs.rb
@@ -14,7 +14,7 @@ def destination_path = 'lfs.tar.gz'
           private
 
           def local
-            ::Backup::Targets::Files.new(nil, storage_path, options: options)
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path)
           end
 
           def storage_path = context.ci_lfs_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/packages.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/packages.rb
index f3bca473d9786..6696a23df42d1 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/packages.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/packages.rb
@@ -14,7 +14,7 @@ def destination_path = 'packages.tar.gz'
           private
 
           def local
-            ::Backup::Targets::Files.new(nil, storage_path, options: options, excludes: ['tmp'])
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path, excludes: ['tmp'])
           end
 
           def storage_path = context.packages_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/pages.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/pages.rb
index f2c12e1d70999..e5715efb33cb1 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/pages.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/pages.rb
@@ -18,7 +18,7 @@ def destination_path = 'pages.tar.gz'
           private
 
           def local
-            ::Backup::Targets::Files.new(nil, storage_path, options: options, excludes: [LEGACY_PAGES_TMP_PATH])
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path, excludes: [LEGACY_PAGES_TMP_PATH])
           end
 
           def storage_path = context.pages_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/registry.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/registry.rb
index 80f97fbb66add..9d6893a969e04 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/registry.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/registry.rb
@@ -13,16 +13,22 @@ def human_name = _('container registry images')
 
           def destination_path = 'registry.tar.gz'
 
+          attr_reader :registry_bucket
+
+          def set_registry_bucket(registry_bucket)
+            @registry_bucket = registry_bucket
+          end
+
           def object_storage?
-            !options.container_registry_bucket.nil?
+            !registry_bucket.nil?
           end
 
           # Registry does not use consolidated object storage config.
           def config
             settings = {
               object_store: {
-                connection: context.config('object_store').connection.to_hash,
-                remote_directory: options.container_registry_bucket
+                connection: context.gitlab_config('object_store').connection.to_hash,
+                remote_directory: registry_bucket
               }
             }
             GitlabSettings::Options.build(settings)
@@ -31,7 +37,7 @@ def config
           private
 
           def local
-            ::Backup::Targets::Files.new(nil, storage_path, options: options)
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path)
           end
 
           def storage_path = context.registry_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/repositories.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/repositories.rb
index f4d172d623e52..5f20f4c233e55 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/repositories.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/repositories.rb
@@ -16,24 +16,7 @@ def destination_optional = true
           private
 
           def target
-            # TODO: migrate to the new codebase and rewrite portions to format output in a readable way
-            ::Backup::Targets::Repositories.new($stdout,
-              strategy: gitaly_strategy,
-              options: options,
-              storages: options.repositories_storages,
-              paths: options.repositories_paths,
-              skip_paths: options.skip_repositories_paths
-            )
-          end
-
-          def gitaly_strategy
-            # TODO: migrate to the new codebase and rewrite portions to format output in a readable way
-            ::Backup::GitalyBackup.new($stdout,
-              incremental: options.incremental?,
-              max_parallelism: options.max_parallelism,
-              storage_parallelism: options.max_storage_parallelism,
-              server_side: false
-            )
+            Gitlab::Backup::Cli::Targets::Repositories.new(context)
           end
         end
       end
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/task.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/task.rb
index 1d1fde51fcda7..81295abea0b7c 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/task.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/task.rb
@@ -5,36 +5,34 @@ module Backup
     module Cli
       module Tasks
         class Task
-          attr_reader :options, :context
           attr_writer :target
+          attr_reader :context
+
+          def initialize(context:)
+            @context = context
+          end
 
           # Identifier used as parameter in the CLI to skip from executing
           def self.id
             raise NotImplementedError
           end
 
-          def initialize(context:, options:)
-            @context = context
-            @options = options
-          end
-
           # Initiate a backup
           #
           # @param [Pathname] backup_path a path where to store the backups
-          # @param [String] backup_id
-          def backup!(backup_path, backup_id)
+          def backup!(backup_path)
             backup_output = backup_path.join(destination_path)
 
             # During test, we ensure storage exists so we can run against `RAILS_ENV=test` environment
-            FileUtils.mkdir_p(storage_path) if context.env.test? && respond_to?(:storage_path, true)
+            FileUtils.mkdir_p(storage_path) if context&.env&.test? && respond_to?(:storage_path, true)
 
-            target.dump(backup_output, backup_id)
+            target.dump(backup_output)
           end
 
-          def restore!(archive_directory, backup_id)
+          def restore!(archive_directory)
             archived_data_location = Pathname(archive_directory).join(destination_path)
 
-            target.restore(archived_data_location, backup_id)
+            target.restore(archived_data_location)
           end
 
           # Key string that identifies the task
@@ -70,7 +68,10 @@ def enabled?
           end
 
           def config
-            context.config(id)
+            return context.config(id) if context
+
+            Output.warning("No context passed to derive configuration from.")
+            nil
           end
 
           def object_storage?
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/terraform_state.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/terraform_state.rb
index be821ca5966dc..01cc7cf18e48f 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/terraform_state.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/terraform_state.rb
@@ -14,7 +14,7 @@ def destination_path = 'terraform_state.tar.gz'
           private
 
           def local
-            ::Backup::Targets::Files.new(nil, storage_path, options: options, excludes: ['tmp'])
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path, excludes: ['tmp'])
           end
 
           def storage_path = context.terraform_state_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/uploads.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/uploads.rb
index 2846528073d1b..e2979fad614c8 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/uploads.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/uploads.rb
@@ -14,7 +14,7 @@ def destination_path = 'uploads.tar.gz'
           private
 
           def local
-            ::Backup::Targets::Files.new(nil, storage_path, options: options, excludes: ['tmp'])
+            Gitlab::Backup::Cli::Targets::Files.new(context, storage_path, excludes: ['tmp'])
           end
 
           def storage_path = context.upload_path
diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils/tar.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils/tar.rb
index afaf32357c42d..aa0bfb6417632 100644
--- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils/tar.rb
+++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils/tar.rb
@@ -49,6 +49,14 @@ def pack_cmd(archive_file:, target_directory:, target:, excludes: [])
             Shell::Command.new(cmd, *tar_args)
           end
 
+          def pack_from_stdin_cmd(target_directory:, target:, excludes: [])
+            pack_cmd(
+              archive_file: '-', # use stdin as list of files
+              target_directory: target_directory,
+              target: target,
+              excludes: excludes)
+          end
+
           # @param [Object] archive_file
           # @param [Object] target_directory
           # @return [Gitlab::Backup::Cli::Shell::Command]
@@ -64,6 +72,11 @@ def extract_cmd(archive_file:, target_directory:)
             Shell::Command.new(cmd, *tar_args)
           end
 
+          def extract_from_stdin_cmd(target_directory:)
+            extract_cmd(archive_file: '-', # use stdin as file source content
+              target_directory: target_directory)
+          end
+
           private
 
           def build_exclude_patterns(*patterns)
diff --git a/gems/gitlab-backup-cli/spec/fixtures/gitlab.yml b/gems/gitlab-backup-cli/spec/fixtures/config/gitlab.yml
similarity index 100%
rename from gems/gitlab-backup-cli/spec/fixtures/gitlab.yml
rename to gems/gitlab-backup-cli/spec/fixtures/config/gitlab.yml
diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/gitlab_config_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/gitlab_config_spec.rb
index a7ec31af651a2..dc0af3c862353 100644
--- a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/gitlab_config_spec.rb
+++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/gitlab_config_spec.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 RSpec.describe Gitlab::Backup::Cli::GitlabConfig do
-  let(:config_fixture) { fixtures_path.join('gitlab.yml') }
+  let(:config_fixture) { fixtures_path.join('config/gitlab.yml') }
 
   subject(:gitlab_config) { described_class.new(config_fixture) }
 
diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/files_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/files_spec.rb
new file mode 100644
index 0000000000000..d9639aee089be
--- /dev/null
+++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/files_spec.rb
@@ -0,0 +1,188 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'active_support/testing/time_helpers'
+
+RSpec.describe Gitlab::Backup::Cli::Targets::Files, feature_category: :backup_restore do
+  include ActiveSupport::Testing::TimeHelpers
+
+  let(:status_0) { instance_double(Process::Status, success?: true, exitstatus: 0) }
+  let(:status_1) { instance_double(Process::Status, success?: false, exitstatus: 1) }
+  let(:status_2) { instance_double(Process::Status, success?: false, exitstatus: 2) }
+  let(:pipeline_status_failed) do
+    Gitlab::Backup::Cli::Shell::Pipeline::Result.new(stderr: 'Cannot mkdir', status_list: [status_1, status_0])
+  end
+
+  let(:tmp_backup_restore_dir) { Dir.mktmpdir('files-target-restore') }
+
+  let(:destination) { 'registry.tar.gz' }
+
+  let(:context) { Gitlab::Backup::Cli::Context.build }
+
+  let!(:workdir) do
+    FileUtils.mkdir_p(context.backup_basedir)
+    Pathname(Dir.mktmpdir('backup', context.backup_basedir))
+  end
+
+  let(:restore_target) { File.realpath(tmp_backup_restore_dir) }
+
+  let(:backup_target) do
+    %w[@pages.tmp lost+found @hashed].each do |folder|
+      path = Pathname(tmp_backup_restore_dir).join(folder, 'something', 'else')
+
+      FileUtils.mkdir_p(path)
+      FileUtils.touch(path.join('artifacts.zip'))
+    end
+
+    File.realpath(tmp_backup_restore_dir)
+  end
+
+  before do
+    allow(FileUtils).to receive(:mv).and_return(true)
+    allow(File).to receive(:exist?).and_return(true)
+  end
+
+  after do
+    FileUtils.rm_rf([restore_target, backup_target, destination], secure: true)
+  end
+
+  describe '#dump' do
+    subject(:files) do
+      described_class.new(context, backup_target, excludes: ['@pages.tmp'])
+    end
+
+    it 'raises no errors' do
+      expect { files.dump(destination) }.not_to raise_error
+    end
+
+    it 'excludes tmp dirs from archive' do
+      expect_next_instance_of(Gitlab::Backup::Cli::Shell::Pipeline) do |pipeline|
+        tar_cmd = pipeline.shell_commands[0]
+
+        expect(tar_cmd.cmd_args).to include('--exclude=lost+found')
+        expect(tar_cmd.cmd_args).to include('--exclude=./@pages.tmp')
+
+        allow(pipeline).to receive(:run!).and_call_original
+      end
+
+      files.dump(destination)
+    end
+
+    it 'raises an error on failure' do
+      expect_next_instance_of(Gitlab::Backup::Cli::Shell::Pipeline::Result) do |result|
+        expect(result).to receive(:success?).and_return(false)
+      end
+
+      expect do
+        files.dump(destination)
+      end.to raise_error(/Failed to create compressed file/)
+    end
+  end
+
+  describe '#restore' do
+    let(:source) { File.join(restore_target, 'backup.tar.gz') }
+    let(:pipeline) { Gitlab::Backup::Cli::Shell::Pipeline.new(Gitlab::Backup::Cli::Shell::Command.new('echo 0')) }
+
+    subject(:files) { described_class.new(context, restore_target) }
+
+    before do
+      FileUtils.touch(source)
+      allow(Gitlab::Backup::Cli::Shell::Pipeline).to receive(:new).and_return(pipeline)
+    end
+
+    context 'when storage path exists' do
+      before do
+        allow(File).to receive(:exist?).with(restore_target).and_return(true)
+      end
+
+      it 'logs a warning about existing files' do
+        expect(Gitlab::Backup::Cli::Output).to receive(:warning).with(/Ignoring existing files/)
+
+        files.restore(source)
+      end
+    end
+
+    context 'when pipeline execution is successful' do
+      before do
+        allow_next_instance_of(Gitlab::Backup::Cli::Shell::Pipeline::Result) do |result|
+          allow(result).to receive(:success?).and_return(true)
+        end
+      end
+
+      it 'does not raise an error' do
+        expect { files.restore(source) }.not_to raise_error
+      end
+    end
+
+    context 'when pipeline execution fails' do
+      before do
+        allow(files).to receive(:dump).and_return(true)
+        allow_next_instance_of(Gitlab::Backup::Cli::Shell::Pipeline) do |pipeline|
+          allow(pipeline).to receive(:run!).and_return(pipeline_status_failed)
+        end
+      end
+
+      it 'raises a FileRestoreError' do
+        expect { files.restore(source) }.to raise_error(Gitlab::Backup::Cli::Errors::FileRestoreError)
+      end
+    end
+
+    context 'when pipeline execution has non-critical warnings' do
+      let(:warning_message) { 'tar: .: Cannot mkdir: No such file or directory' }
+
+      before do
+        allow_next_instance_of(Gitlab::Backup::Cli::Shell::Pipeline::Result) do |result|
+          allow(result).to receive(:success?).and_return(false)
+          allow(result).to receive(:stderr).and_return(warning_message)
+          allow(result).to receive(:status_list).and_return([status_0, status_2])
+        end
+      end
+
+      it 'does not raise an error' do
+        expect { files.restore(source) }.not_to raise_error
+      end
+    end
+  end
+
+  describe '#ignore_non_success?' do
+    subject(:files) do
+      described_class.new(context, '/var/gitlab-registry')
+    end
+
+    context 'if `tar` command exits with 1 exitstatus' do
+      it 'returns true' do
+        expect(
+          files.send(:ignore_non_success?, 1, nil)
+        ).to be_truthy
+      end
+
+      it 'outputs a warning' do
+        expect do
+          files.send(:ignore_non_success?, 1, nil)
+        end.to output(/Ignoring tar exit status 1/).to_stdout
+      end
+    end
+
+    context 'if `tar` command exits with 2 exitstatus with non-critical warning' do
+      it 'returns true' do
+        expect(
+          files.send(:ignore_non_success?, 2, 'gtar: .: Cannot mkdir: No such file or directory')
+        ).to be_truthy
+      end
+
+      it 'outputs a warning' do
+        expect do
+          files.send(:ignore_non_success?, 2, 'gtar: .: Cannot mkdir: No such file or directory')
+        end.to output(/Ignoring non-success exit status/).to_stdout
+      end
+    end
+
+    context 'if `tar` command exits with any other unlisted error' do
+      it 'returns false' do
+        expect(
+          files.send(:ignore_non_success?, 2, 'unlisted_error')
+        ).to be_falsey
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/gitaly_backup_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/gitaly_backup_spec.rb
new file mode 100644
index 0000000000000..ab8ad369910b4
--- /dev/null
+++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/gitaly_backup_spec.rb
@@ -0,0 +1,199 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'open3'
+
+RSpec.describe Gitlab::Backup::Cli::Targets::GitalyBackup do
+  let(:context) { Gitlab::Backup::Cli::Context.build }
+  let(:gitaly_backup) { described_class.new(context) }
+
+  describe '#start' do
+    context 'when creating a backup' do
+      it 'starts the gitaly-backup process with the correct arguments' do
+        backup_repos_path = '/path/to/backup/repos'
+        backup_id = 'abc123'
+        expected_args = ['create', '-path', backup_repos_path, '-layout', 'manifest', '-id', backup_id]
+        expect(Open3).to receive(:popen2).with(instance_of(Hash), instance_of(String), *expected_args)
+
+        gitaly_backup.start(:create, backup_repos_path, backup_id: backup_id)
+      end
+    end
+
+    context 'when restoring a backup' do
+      it 'starts the gitaly-backup process with the correct arguments' do
+        backup_repos_path = '/path/to/backup/repos'
+        backup_id = 'abc123'
+        remove_all_repositories = %w[repo1 repo2]
+        expected_args = ['restore', '-path', backup_repos_path, '-layout', 'manifest', '-remove-all-repositories',
+          'repo1,repo2', '-id', backup_id]
+        expect(Open3).to receive(:popen2).with(instance_of(Hash), instance_of(String), *expected_args)
+
+        gitaly_backup.start(:restore, backup_repos_path, backup_id: backup_id,
+          remove_all_repositories: remove_all_repositories)
+      end
+    end
+
+    context 'when an invalid type is provided' do
+      it 'raises an error' do
+        expect do
+          gitaly_backup.start(:invalid,
+            '/path/to/backup/repos')
+        end.to raise_error(Gitlab::Backup::Cli::Errors::GitalyBackupError, /unknown backup type: invalid/)
+      end
+    end
+
+    context 'when already started' do
+      it 'raises an error' do
+        gitaly_backup.instance_variable_set(:@thread, Thread.new { true })
+        expect do
+          gitaly_backup.start(:create,
+            '/path/to/backup/repos')
+        end.to raise_error(Gitlab::Backup::Cli::Errors::GitalyBackupError, /already started/)
+      end
+    end
+  end
+
+  describe '#finish!' do
+    context 'when not started' do
+      it 'returns without raising an error' do
+        expect { gitaly_backup.finish! }.not_to raise_error
+      end
+    end
+
+    context 'when started' do
+      let(:thread) { instance_double('Thread', join: nil, value: instance_double(Process::Status, exitstatus: 0)) }
+
+      before do
+        gitaly_backup.instance_variable_set(:@thread, thread)
+        gitaly_backup.instance_variable_set(:@input_stream, instance_double('InputStream', close: nil))
+      end
+
+      it 'closes the input stream and joins the thread' do
+        input_stream = gitaly_backup.instance_variable_get(:@input_stream)
+        expect(input_stream).to receive(:close)
+        expect(thread).to receive(:join)
+
+        gitaly_backup.finish!
+      end
+
+      context 'when the process exits with a non-zero status' do
+        let(:thread) { instance_double('Thread', join: nil, value: instance_double(Process::Status, exitstatus: 1)) }
+
+        it 'raises an error' do
+          expect do
+            gitaly_backup.finish!
+          end.to raise_error(Gitlab::Backup::Cli::Errors::GitalyBackupError, /gitaly-backup exit status 1/)
+        end
+      end
+    end
+  end
+
+  describe '#enqueue' do
+    context 'when not started' do
+      it 'raises an error' do
+        expect do
+          gitaly_backup.enqueue(double, :project)
+        end.to raise_error(Gitlab::Backup::Cli::Errors::GitalyBackupError, /not started/)
+      end
+    end
+
+    context 'when started' do
+      let(:input_stream) { instance_double('InputStream', puts: nil) }
+
+      before do
+        gitaly_backup.instance_variable_set(:@input_stream, input_stream)
+        gitaly_backup.instance_variable_set(:@thread, Thread.new { true })
+      end
+
+      context 'with a project repository' do
+        let(:container) do
+          instance_double('Project', repository_storage: 'storage', disk_path: 'disk/path', full_path: 'group/project')
+        end
+
+        it 'schedules a backup job with the correct parameters' do
+          expected_json = {
+            storage_name: 'storage',
+            relative_path: 'disk/path',
+            gl_project_path: 'group/project',
+            always_create: true
+          }.to_json
+
+          expect(input_stream).to receive(:puts).with(expected_json)
+
+          gitaly_backup.enqueue(container, :project)
+        end
+      end
+
+      context 'with a wiki repository' do
+        let(:wiki) do
+          instance_double('Wiki', repository_storage: 'wiki_storage', disk_path: 'wiki/disk/path',
+            full_path: 'group/project.wiki')
+        end
+
+        let(:container) { instance_double('Project', wiki: wiki) }
+
+        it 'schedules a backup job with the correct parameters' do
+          expected_json = {
+            storage_name: 'wiki_storage',
+            relative_path: 'wiki/disk/path',
+            gl_project_path: 'group/project.wiki',
+            always_create: false
+          }.to_json
+
+          expect(input_stream).to receive(:puts).with(expected_json)
+
+          gitaly_backup.enqueue(container, :wiki)
+        end
+      end
+
+      context 'with a snippet repository' do
+        let(:container) do
+          instance_double('Snippet', repository_storage: 'storage', disk_path: 'disk/path', full_path: 'snippets/1')
+        end
+
+        it 'schedules a backup job with the correct parameters' do
+          expected_json = {
+            storage_name: 'storage',
+            relative_path: 'disk/path',
+            gl_project_path: 'snippets/1',
+            always_create: false
+          }.to_json
+
+          expect(input_stream).to receive(:puts).with(expected_json)
+
+          gitaly_backup.enqueue(container, :snippet)
+        end
+      end
+
+      context 'with a design repository' do
+        let(:project) { instance_double('Project', disk_path: 'disk/path', full_path: 'group/project') }
+        let(:container) do
+          instance_double('DesignRepository', project: project,
+            repository: instance_double('Repository', repository_storage: 'storage'))
+        end
+
+        it 'schedules a backup job with the correct parameters' do
+          expected_json = {
+            storage_name: 'storage',
+            relative_path: 'disk/path',
+            gl_project_path: 'group/project',
+            always_create: false
+          }.to_json
+
+          expect(input_stream).to receive(:puts).with(expected_json)
+
+          gitaly_backup.enqueue(container, :design)
+        end
+      end
+
+      context 'with an invalid repository type' do
+        it 'raises an error' do
+          expect do
+            gitaly_backup.enqueue(nil,
+              :invalid)
+          end.to raise_error(Gitlab::Backup::Cli::Errors::GitalyBackupError, /no container for repo type/)
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/object_storage/google_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/object_storage/google_spec.rb
index 1ef0090d93a25..f034ff184fadd 100644
--- a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/object_storage/google_spec.rb
+++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/object_storage/google_spec.rb
@@ -55,17 +55,15 @@
     }
   end
 
-  let(:backup_options) { instance_double("::Backup::Options", remote_directory: 'fake_backup_bucket') }
-
   before do
     allow(Gitlab).to receive(:config).and_return(gitlab_config)
     allow(::Google::Cloud::StorageTransfer).to receive(:storage_transfer_service).and_return(client)
     allow(gitlab_config).to receive(:[]).with('fake_object').and_return(supported_config)
   end
 
-  subject(:object_storage) { described_class.new("fake_object", backup_options, supported_config) }
+  subject(:object_storage) { described_class.new("fake_object", 'fake_backup_bucket', supported_config) }
 
-  describe "#dump" do
+  describe "#dump", :silence_output do
     context "when job exists" do
       before do
         allow(client).to receive(:get_transfer_job).and_return(backup_transfer_job)
@@ -79,7 +77,7 @@
           transfer_job: updated_spec
         )
         expect(client).to receive(:run_transfer_job).with({ job_name: "fake_transfer_job", project_id: "fake_project" })
-        object_storage.dump(nil, 12345)
+        object_storage.dump(12345)
       end
     end
 
@@ -94,12 +92,12 @@
       it "creates a new job" do
         expect(client).to receive(:create_transfer_job)
           .with(transfer_job: new_backup_transfer_job_spec).and_return(backup_transfer_job)
-        object_storage.dump(nil, 12345)
+        object_storage.dump(12345)
       end
     end
   end
 
-  describe "#restore" do
+  describe "#restore", :silence_output do
     context "when job exists" do
       before do
         allow(client).to receive(:get_transfer_job).and_return(restore_transfer_job)
@@ -113,7 +111,7 @@
           transfer_job: updated_spec
         )
         expect(client).to receive(:run_transfer_job).with({ job_name: "fake_transfer_job", project_id: "fake_project" })
-        object_storage.restore(nil, 12345)
+        object_storage.restore(12345)
       end
     end
 
@@ -128,7 +126,7 @@
       it "creates a new job" do
         expect(client).to receive(:create_transfer_job)
           .with(transfer_job: new_restore_transfer_job_spec).and_return(restore_transfer_job)
-        object_storage.restore(nil, 12345)
+        object_storage.restore(12345)
       end
     end
   end
diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/repositories_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/repositories_spec.rb
new file mode 100644
index 0000000000000..160dd1802307a
--- /dev/null
+++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/repositories_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Backup::Cli::Targets::Repositories do
+  let(:context) { Gitlab::Backup::Cli::Context.build }
+  let(:strategy) { repo_target.strategy }
+
+  subject(:repo_target) { described_class.new(context) }
+
+  describe '#dump' do
+    it 'starts and finishes the strategy' do
+      expect(strategy).to receive(:start).with(:create, '/path/to/destination')
+      expect(repo_target).to receive(:enqueue_consecutive)
+      expect(strategy).to receive(:finish!)
+
+      repo_target.dump('/path/to/destination')
+    end
+  end
+
+  describe '#restore' do
+    it 'starts and finishes the strategy' do
+      expect(strategy).to receive(:start).with(:restore, '/path/to/destination', remove_all_repositories: ["default"])
+      expect(repo_target).to receive(:enqueue_consecutive)
+      expect(strategy).to receive(:finish!)
+      expect(repo_target).to receive(:restore_object_pools)
+
+      repo_target.restore('/path/to/destination')
+    end
+  end
+
+  describe '#enqueue_consecutive' do
+    it 'calls enqueue_consecutive_projects and enqueue_consecutive_snippets' do
+      expect(repo_target).to receive(:enqueue_consecutive_projects)
+      expect(repo_target).to receive(:enqueue_consecutive_snippets)
+
+      repo_target.send(:enqueue_consecutive)
+    end
+  end
+
+  describe '#enqueue_project' do
+    let(:project) { instance_double('Project', design_management_repository: nil) }
+
+    it 'enqueues project and wiki' do
+      expect(strategy).to receive(:enqueue).with(project, Gitlab::Backup::Cli::RepoType::PROJECT)
+      expect(strategy).to receive(:enqueue).with(project, Gitlab::Backup::Cli::RepoType::WIKI)
+
+      repo_target.send(:enqueue_project, project)
+    end
+
+    context 'when project has design management repository' do
+      let(:design_repo) { instance_double('DesignRepository') }
+      let(:project) { instance_double('Project', design_management_repository: design_repo) }
+
+      it 'enqueues project, wiki, and design' do
+        expect(strategy).to receive(:enqueue).with(project, Gitlab::Backup::Cli::RepoType::PROJECT)
+        expect(strategy).to receive(:enqueue).with(project, Gitlab::Backup::Cli::RepoType::WIKI)
+        expect(strategy).to receive(:enqueue).with(design_repo, Gitlab::Backup::Cli::RepoType::DESIGN)
+
+        repo_target.send(:enqueue_project, project)
+      end
+    end
+  end
+
+  describe '#enqueue_snippet' do
+    let(:snippet) { instance_double('Snippet') }
+
+    it 'enqueues the snippet' do
+      expect(strategy).to receive(:enqueue).with(snippet, Gitlab::Backup::Cli::RepoType::SNIPPET)
+
+      repo_target.send(:enqueue_snippet, snippet)
+    end
+  end
+end
diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/tasks/task_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/tasks/task_spec.rb
index 2148df8cafd9d..2ac3cc4e96e62 100644
--- a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/tasks/task_spec.rb
+++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/tasks/task_spec.rb
@@ -1,12 +1,11 @@
 # frozen_string_literal: true
 
 RSpec.describe Gitlab::Backup::Cli::Tasks::Task do
-  let(:options) { instance_double("::Backup::Option", backup_id: "abc123") }
   let(:context) { build_fake_context }
   let(:tmpdir) { Pathname.new(Dir.mktmpdir('task', temp_path)) }
   let(:metadata) { build(:backup_metadata) }
 
-  subject(:task) { described_class.new(options: options, context: context) }
+  subject(:task) { described_class.new(context: context) }
 
   after do
     FileUtils.rmtree(tmpdir)
@@ -37,9 +36,9 @@
       end
     end
 
-    describe '#target' do
+    describe '#local' do
       it 'raises an error' do
-        expect { task.send(:target) }.to raise_error(NotImplementedError)
+        expect { task.send(:local) }.to raise_error(NotImplementedError)
       end
     end
   end
@@ -49,7 +48,7 @@
       expect(task).to receive(:destination_path).and_return(tmpdir.join('test_task'))
       expect(task).to receive_message_chain(:target, :dump)
 
-      task.backup!(tmpdir, metadata.backup_id)
+      task.backup!(tmpdir)
     end
   end
 
@@ -59,7 +58,7 @@
       expect(task).to receive(:destination_path).and_return(tmpdir.join('test_task'))
       expect(task).to receive_message_chain(:target, :restore)
 
-      task.restore!(archive_directory, options.backup_id)
+      task.restore!(archive_directory)
     end
   end
 end
diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/utils/tar_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/utils/tar_spec.rb
index 374ea69281e02..fc0fd8e374f0b 100644
--- a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/utils/tar_spec.rb
+++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/utils/tar_spec.rb
@@ -20,9 +20,13 @@ def tar_tempdir
         target_basepath = tempdir
         target = tempdir.join('*')
 
+        result = nil
+
         expect do
-          tar.pack_cmd(archive_file: archive_file, target_directory: target_basepath, target: target)
+          result = tar.pack_cmd(archive_file: archive_file, target_directory: target_basepath, target: target)
         end.not_to raise_exception
+
+        expect(result).to be_a(Gitlab::Backup::Cli::Shell::Command)
       end
     end
 
@@ -83,4 +87,51 @@ def tar_tempdir
       end
     end
   end
+
+  describe '#pack_from_stdin_cmd' do
+    it 'delegates parameters to pack_cmd passing archive_files: as -' do
+      tar_tempdir do |tempdir|
+        target_basepath = tempdir
+        target = tempdir.join('*')
+        excludes = ['lost+found']
+
+        expect(tar).to receive(:pack_cmd).with(
+          archive_file: '-',
+          target_directory: target_basepath,
+          target: target,
+          excludes: excludes)
+
+        tar.pack_from_stdin_cmd(target_directory: target_basepath, target: target, excludes: excludes)
+      end
+    end
+  end
+
+  describe '#extract_cmd' do
+    it 'instantiate a Shell::Command with default required params' do
+      tar_tempdir do |tempdir|
+        archive_file = tempdir.join('testarchive.tar')
+        target_basepath = tempdir
+
+        result = nil
+
+        expect do
+          result = tar.extract_cmd(archive_file: archive_file, target_directory: target_basepath)
+        end.not_to raise_exception
+
+        expect(result).to be_a(Gitlab::Backup::Cli::Shell::Command)
+      end
+    end
+  end
+
+  describe 'extract_from_stdin_cmd' do
+    it 'delegates parameters to extract_cmd passing archive_files: as -' do
+      tar_tempdir do |tempdir|
+        target_basepath = tempdir
+
+        expect(tar).to receive(:extract_cmd).with(archive_file: '-', target_directory: target_basepath)
+
+        tar.extract_from_stdin_cmd(target_directory: target_basepath)
+      end
+    end
+  end
 end
diff --git a/gems/gitlab-backup-cli/spec/spec_helper.rb b/gems/gitlab-backup-cli/spec/spec_helper.rb
index a43e0477bdd63..870a795ce4453 100644
--- a/gems/gitlab-backup-cli/spec/spec_helper.rb
+++ b/gems/gitlab-backup-cli/spec/spec_helper.rb
@@ -7,6 +7,7 @@
 require 'gitlab/rspec/next_instance_of'
 
 ENV["RAILS_ENV"] ||= "test"
+GITLAB_PATH = File.expand_path(File.join(__dir__, '/fixtures/'))
 
 # Load spec support code
 Dir['spec/support/**/*.rb'].each { |f| load f }
diff --git a/gems/gitlab-backup-cli/spec/support/shared_examples/context_shared_examples.rb b/gems/gitlab-backup-cli/spec/support/shared_examples/context_shared_examples.rb
index 1aa7874fbb782..b5e6e6ededbb8 100644
--- a/gems/gitlab-backup-cli/spec/support/shared_examples/context_shared_examples.rb
+++ b/gems/gitlab-backup-cli/spec/support/shared_examples/context_shared_examples.rb
@@ -24,7 +24,7 @@
   describe '#backup_basedir' do
     context 'with a relative path configured in gitlab.yml' do
       it 'returns a full path based on gitlab basepath' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.backup_basedir).to eq(fake_gitlab_basepath.join('tmp/tests/backups'))
       end
@@ -58,7 +58,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.ci_builds_path).to eq(Pathname('/tmp/gitlab/full/builds'))
       end
@@ -84,7 +84,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.ci_job_artifacts_path).to eq(Pathname('/tmp/gitlab/full/artifacts'))
       end
@@ -110,7 +110,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.ci_secure_files_path).to eq(Pathname('/tmp/gitlab/full/ci_secure_files'))
       end
@@ -136,7 +136,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.ci_lfs_path).to eq(Pathname('/tmp/gitlab/full/lfs-objects'))
       end
@@ -162,7 +162,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.packages_path).to eq(Pathname('/tmp/gitlab/full/packages'))
       end
@@ -188,7 +188,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.pages_path).to eq(Pathname('/tmp/gitlab/full/pages'))
       end
@@ -214,7 +214,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.registry_path).to eq(Pathname('/tmp/gitlab/full/registry'))
       end
@@ -240,7 +240,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.terraform_state_path).to eq(Pathname('/tmp/gitlab/full/terraform_state'))
       end
@@ -266,7 +266,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.upload_path).to eq(Pathname('/tmp/gitlab/full/public/uploads'))
       end
@@ -292,7 +292,7 @@
 
     context 'with a full path configured in gitlab.yml' do
       it 'returns a full path as configured in gitlab.yml' do
-        use_gitlab_config_fixture('gitlab.yml')
+        use_gitlab_config_fixture('config/gitlab.yml')
 
         expect(context.send(:gitlab_shared_path)).to eq(Pathname('/tmp/gitlab/full/shared'))
       end
diff --git a/shared/artifacts/tmp/cache/.gitkeep b/shared/artifacts/tmp/cache/.gitkeep
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/shared/artifacts/tmp/uploads/.gitkeep b/shared/artifacts/tmp/uploads/.gitkeep
deleted file mode 100644
index e69de29bb2d1d..0000000000000
-- 
GitLab