Rollback project folder move after error in Projects::TransferService
This commit is contained in:
parent
f07aee72be
commit
6db1a28016
3 changed files with 141 additions and 42 deletions
|
@ -12,87 +12,121 @@ module Projects
|
||||||
TransferError = Class.new(StandardError)
|
TransferError = Class.new(StandardError)
|
||||||
|
|
||||||
def execute(new_namespace)
|
def execute(new_namespace)
|
||||||
if new_namespace.blank?
|
@new_namespace = new_namespace
|
||||||
|
|
||||||
|
if @new_namespace.blank?
|
||||||
raise TransferError, 'Please select a new namespace for your project.'
|
raise TransferError, 'Please select a new namespace for your project.'
|
||||||
end
|
end
|
||||||
unless allowed_transfer?(current_user, project, new_namespace)
|
|
||||||
|
unless allowed_transfer?(current_user, project)
|
||||||
raise TransferError, 'Transfer failed, please contact an admin.'
|
raise TransferError, 'Transfer failed, please contact an admin.'
|
||||||
end
|
end
|
||||||
transfer(project, new_namespace)
|
|
||||||
|
transfer(project)
|
||||||
|
|
||||||
|
true
|
||||||
rescue Projects::TransferService::TransferError => ex
|
rescue Projects::TransferService::TransferError => ex
|
||||||
project.reload
|
project.reload
|
||||||
project.errors.add(:new_namespace, ex.message)
|
project.errors.add(:new_namespace, ex.message)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def transfer(project, new_namespace)
|
private
|
||||||
old_namespace = project.namespace
|
|
||||||
|
|
||||||
Project.transaction do
|
def transfer(project)
|
||||||
old_path = project.path_with_namespace
|
@old_path = project.path_with_namespace
|
||||||
old_group = project.group
|
@old_group = project.group
|
||||||
new_path = File.join(new_namespace.try(:full_path) || '', project.path)
|
@new_path = File.join(@new_namespace.try(:full_path) || '', project.path)
|
||||||
|
@old_namespace = project.namespace
|
||||||
|
|
||||||
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
|
if Project.where(path: project.path, namespace_id: @new_namespace.try(:id)).exists?
|
||||||
raise TransferError.new("Project with same path in target namespace already exists")
|
raise TransferError.new("Project with same path in target namespace already exists")
|
||||||
end
|
end
|
||||||
|
|
||||||
if project.has_container_registry_tags?
|
if project.has_container_registry_tags?
|
||||||
# we currently doesn't support renaming repository if it contains tags in container registry
|
# We currently don't support renaming repository if it contains tags in container registry
|
||||||
raise TransferError.new('Project cannot be transferred, because tags are present in its container registry')
|
raise TransferError.new('Project cannot be transferred, because tags are present in its container registry')
|
||||||
end
|
end
|
||||||
|
|
||||||
project.expire_caches_before_rename(old_path)
|
attempt_transfer_transaction
|
||||||
|
end
|
||||||
|
|
||||||
# Apply new namespace id and visibility level
|
def attempt_transfer_transaction
|
||||||
project.namespace = new_namespace
|
Project.transaction do
|
||||||
project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
|
project.expire_caches_before_rename(@old_path)
|
||||||
project.save!
|
|
||||||
|
update_namespace_and_visibility(@new_namespace)
|
||||||
|
|
||||||
# Notifications
|
# Notifications
|
||||||
project.send_move_instructions(old_path)
|
project.send_move_instructions(@old_path)
|
||||||
|
|
||||||
# Move main repository
|
# Move main repository
|
||||||
unless gitlab_shell.mv_repository(project.repository_storage_path, old_path, new_path)
|
unless move_repo_folder(@old_path, @new_path)
|
||||||
raise TransferError.new('Cannot move project')
|
raise TransferError.new('Cannot move project')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Move wiki repo also if present
|
# Move wiki repo also if present
|
||||||
gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki")
|
move_repo_folder("#{@old_path}.wiki", "#{@new_path}.wiki")
|
||||||
|
|
||||||
# Move missing group labels to project
|
# Move missing group labels to project
|
||||||
Labels::TransferService.new(current_user, old_group, project).execute
|
Labels::TransferService.new(current_user, @old_group, project).execute
|
||||||
|
|
||||||
# Move uploads
|
# Move uploads
|
||||||
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.full_path, new_namespace.full_path)
|
Gitlab::UploadsTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
|
||||||
|
|
||||||
# Move pages
|
# Move pages
|
||||||
Gitlab::PagesTransfer.new.move_project(project.path, old_namespace.full_path, new_namespace.full_path)
|
Gitlab::PagesTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
|
||||||
|
|
||||||
project.old_path_with_namespace = old_path
|
project.old_path_with_namespace = @old_path
|
||||||
|
|
||||||
SystemHooksService.new.execute_hooks_for(project, :transfer)
|
execute_system_hooks
|
||||||
|
end
|
||||||
|
rescue Exception # rubocop:disable Lint/RescueException
|
||||||
|
rollback_side_effects
|
||||||
|
raise
|
||||||
|
ensure
|
||||||
|
refresh_permissions
|
||||||
end
|
end
|
||||||
|
|
||||||
refresh_permissions(old_namespace, new_namespace)
|
def allowed_transfer?(current_user, project)
|
||||||
|
@new_namespace &&
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def allowed_transfer?(current_user, project, namespace)
|
|
||||||
namespace &&
|
|
||||||
can?(current_user, :change_namespace, project) &&
|
can?(current_user, :change_namespace, project) &&
|
||||||
namespace.id != project.namespace_id &&
|
@new_namespace.id != project.namespace_id &&
|
||||||
current_user.can?(:create_projects, namespace)
|
current_user.can?(:create_projects, @new_namespace)
|
||||||
end
|
end
|
||||||
|
|
||||||
def refresh_permissions(old_namespace, new_namespace)
|
def update_namespace_and_visibility(to_namespace)
|
||||||
|
# Apply new namespace id and visibility level
|
||||||
|
project.namespace = to_namespace
|
||||||
|
project.visibility_level = to_namespace.visibility_level unless project.visibility_level_allowed_by_group?
|
||||||
|
project.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh_permissions
|
||||||
# This ensures we only schedule 1 job for every user that has access to
|
# This ensures we only schedule 1 job for every user that has access to
|
||||||
# the namespaces.
|
# the namespaces.
|
||||||
user_ids = old_namespace.user_ids_for_project_authorizations |
|
user_ids = @old_namespace.user_ids_for_project_authorizations |
|
||||||
new_namespace.user_ids_for_project_authorizations
|
@new_namespace.user_ids_for_project_authorizations
|
||||||
|
|
||||||
UserProjectAccessChangedService.new(user_ids).execute
|
UserProjectAccessChangedService.new(user_ids).execute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def rollback_side_effects
|
||||||
|
rollback_folder_move
|
||||||
|
update_namespace_and_visibility(@old_namespace)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rollback_folder_move
|
||||||
|
move_repo_folder(@new_path, @old_path)
|
||||||
|
move_repo_folder("#{@new_path}.wiki", "#{@old_path}.wiki")
|
||||||
|
end
|
||||||
|
|
||||||
|
def move_repo_folder(from_name, to_name)
|
||||||
|
gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute_system_hooks
|
||||||
|
SystemHooksService.new.execute_hooks_for(project, :transfer)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Rollback project repo move if there is an error in Projects::TransferService
|
||||||
|
merge_request: 11877
|
||||||
|
author:
|
|
@ -19,6 +19,67 @@ describe Projects::TransferService, services: true do
|
||||||
it { expect(project.namespace).to eq(group) }
|
it { expect(project.namespace).to eq(group) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when transfer succeeds' do
|
||||||
|
before do
|
||||||
|
group.add_owner(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends notifications' do
|
||||||
|
expect_any_instance_of(NotificationService).to receive(:project_was_moved)
|
||||||
|
|
||||||
|
transfer_project(project, user, group)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'executes system hooks' do
|
||||||
|
expect_any_instance_of(Projects::TransferService).to receive(:execute_system_hooks)
|
||||||
|
|
||||||
|
transfer_project(project, user, group)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when transfer fails' do
|
||||||
|
let!(:original_path) { project_path(project) }
|
||||||
|
|
||||||
|
def attempt_project_transfer
|
||||||
|
expect do
|
||||||
|
transfer_project(project, user, group)
|
||||||
|
end.to raise_error(ActiveRecord::ActiveRecordError)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
group.add_owner(user)
|
||||||
|
|
||||||
|
expect_any_instance_of(Labels::TransferService).to receive(:execute).and_raise(ActiveRecord::StatementInvalid, "PG ERROR")
|
||||||
|
end
|
||||||
|
|
||||||
|
def project_path(project)
|
||||||
|
File.join(project.repository_storage_path, "#{project.path_with_namespace}.git")
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_path
|
||||||
|
project_path(project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rolls back repo location' do
|
||||||
|
attempt_project_transfer
|
||||||
|
|
||||||
|
expect(Dir.exist?(original_path)).to be_truthy
|
||||||
|
expect(original_path).to eq current_path
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send move notifications" do
|
||||||
|
expect_any_instance_of(NotificationService).not_to receive(:project_was_moved)
|
||||||
|
|
||||||
|
attempt_project_transfer
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't run system hooks" do
|
||||||
|
expect_any_instance_of(Projects::TransferService).not_to receive(:execute_system_hooks)
|
||||||
|
|
||||||
|
attempt_project_transfer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'namespace -> no namespace' do
|
context 'namespace -> no namespace' do
|
||||||
before do
|
before do
|
||||||
@result = transfer_project(project, user, nil)
|
@result = transfer_project(project, user, nil)
|
||||||
|
|
Loading…
Reference in a new issue