From ffdb3f26e7f8f9b62fd32b52c30edf167c760793 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Fri, 12 Oct 2018 14:40:44 +0200 Subject: [PATCH] Migration to write fullpath in all repository configs In https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16027 it was added to write `gitlab.fullpath` in the git config of all repositories. But this only writes them on move or migrate to hashed storage. This adds a migration that writes the fullpath to all the repositories. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/41776 --- .../tc-backfill-full-path-config.yml | 5 + ...ackfill_store_project_full_path_in_repo.rb | 136 ++++++++++++++++++ ...ll_store_project_full_path_in_repo_spec.rb | 70 +++++++++ 3 files changed, 211 insertions(+) create mode 100644 changelogs/unreleased/tc-backfill-full-path-config.yml create mode 100644 db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb create mode 100644 spec/migrations/backfill_store_project_full_path_in_repo_spec.rb diff --git a/changelogs/unreleased/tc-backfill-full-path-config.yml b/changelogs/unreleased/tc-backfill-full-path-config.yml new file mode 100644 index 00000000000..4f06284d0e3 --- /dev/null +++ b/changelogs/unreleased/tc-backfill-full-path-config.yml @@ -0,0 +1,5 @@ +--- +title: Migration to write fullpath in all repository configs +merge_request: 22322 +author: +type: other diff --git a/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb b/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb new file mode 100644 index 00000000000..1e2e4527d2b --- /dev/null +++ b/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +class BackfillStoreProjectFullPathInRepo < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + class Repository + attr_reader :storage + + def initialize(storage, relative_path) + @storage = storage + @relative_path = relative_path + end + + def gitaly_repository + Gitaly::Repository.new(storage_name: @storage, relative_path: @relative_path) + end + end + + module Storage + class HashedProject + attr_accessor :project + + ROOT_PATH_PREFIX = '@hashed'.freeze + + def initialize(project) + @project = project + end + + def disk_path + "#{ROOT_PATH_PREFIX}/#{disk_hash[0..1]}/#{disk_hash[2..3]}/#{disk_hash}" + end + + def disk_hash + @disk_hash ||= Digest::SHA2.hexdigest(project.id.to_s) if project.id + end + end + + class LegacyProject + attr_accessor :project + + def initialize(project) + @project = project + end + + def disk_path + project.full_path + end + end + end + + module Routable + extend ActiveSupport::Concern + + def full_path + @full_path ||= build_full_path + end + + def build_full_path + if parent && path + parent.full_path + '/' + path + else + path + end + end + end + + class Namespace < ActiveRecord::Base + self.table_name = 'namespaces' + + include Routable + + belongs_to :parent, class_name: "Namespace" + end + + class Project < ActiveRecord::Base + self.table_name = 'projects' + + include Routable + include EachBatch + + FULLPATH_CONFIG_KEY = 'gitlab.fullpath' + + belongs_to :namespace + delegate :disk_path, to: :storage + alias_method :parent, :namespace + + def add_fullpath_config + entries = { FULLPATH_CONFIG_KEY => full_path } + + repository_service.set_config(entries) + end + + def remove_fullpath_config + repository_service.delete_config([FULLPATH_CONFIG_KEY]) + end + + def storage + @storage ||= + if hashed_storage? + Storage::HashedProject.new(self) + else + Storage::LegacyProject.new(self) + end + end + + def hashed_storage? + self.storage_version && self.storage_version >= 1 + end + + def repository + @repository ||= Repository.new(repository_storage, disk_path + '.git') + end + + def repository_service + @repository_service ||= Gitlab::GitalyClient::RepositoryService.new(repository) + end + end + + def up + Project.each_batch do |batch| + batch.each do |project| + project.add_fullpath_config + end + end + end + + def down + Project.each_batch do |batch| + batch.each do |project| + project.remove_fullpath_config + end + end + end +end diff --git a/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb b/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb new file mode 100644 index 00000000000..f2651f8a02f --- /dev/null +++ b/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require Rails.root.join('db', 'post_migrate', '20181010133639_backfill_store_project_full_path_in_repo.rb') + +describe BackfillStoreProjectFullPathInRepo, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:group) { namespaces.create!(name: 'foo', path: 'foo') } + let(:subgroup) { namespaces.create!(name: 'bar', path: 'bar', parent_id: group.id) } + + subject(:migration) { described_class.new } + + describe '#up' do + shared_examples_for 'writes the full path to git config' do + it 'writes the git config' do + expect_any_instance_of(Gitlab::GitalyClient::RepositoryService) + .to receive(:set_config).with('gitlab.fullpath' => expected_path) + + migration.up + end + + context 'legacy storage' do + it 'finds the repository at the correct location' do + Project.find(project.id).create_repository + + expect { migration.up }.not_to raise_error + end + end + + context 'hashed storage' do + it 'finds the repository at the correct location' do + project.update_attribute(:storage_version, 1) + + Project.find(project.id).create_repository + + expect { migration.up }.not_to raise_error + end + end + end + + context 'project in group' do + let!(:project) { projects.create!(namespace_id: group.id, name: 'baz', path: 'baz') } + let(:expected_path) { 'foo/baz' } + + it_behaves_like 'writes the full path to git config' + end + + context 'project in subgroup' do + let!(:project) { projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz') } + let(:expected_path) { 'foo/bar/baz' } + + it_behaves_like 'writes the full path to git config' + end + end + + describe '#down' do + context 'project in group' do + let!(:project) { projects.create!(namespace_id: group.id, name: 'baz', path: 'baz') } + + it 'deletes the gitlab full config value' do + expect_any_instance_of(Gitlab::GitalyClient::RepositoryService) + .to receive(:delete_config).with(['gitlab.fullpath']) + + migration.down + end + end + end +end