Commit a537e560 authored by Arturo Herrero's avatar Arturo Herrero

Transfer a project/group inheriting integrations

The approach to transfer a project/group under a new namespace (group)
is the following:
1. Remove the current services.
2. Re-create them again (now with the new settings if they exist under
   the new group).
3. For groups, propagate the new settings to subgroups and projects
   belonging to the group.

It also exclude overriding integration with custom values.
parent fdb230f5
...@@ -70,6 +70,7 @@ class Service < ApplicationRecord ...@@ -70,6 +70,7 @@ class Service < ApplicationRecord
scope :by_type, -> (type) { where(type: type) } scope :by_type, -> (type) { where(type: type) }
scope :by_active_flag, -> (flag) { where(active: flag) } scope :by_active_flag, -> (flag) { where(active: flag) }
scope :inherit_from_id, -> (id) { where(inherit_from_id: id) } scope :inherit_from_id, -> (id) { where(inherit_from_id: id) }
scope :inherit, -> { where.not(inherit_from_id: nil) }
scope :for_group, -> (group) { where(group_id: group, type: available_services_types(include_project_specific: false)) } scope :for_group, -> (group) { where(group_id: group, type: available_services_types(include_project_specific: false)) }
scope :for_template, -> { where(template: true, type: available_services_types(include_project_specific: false)) } scope :for_template, -> { where(template: true, type: available_services_types(include_project_specific: false)) }
scope :for_instance, -> { where(instance: true, type: available_services_types(include_project_specific: false)) } scope :for_instance, -> { where(instance: true, type: available_services_types(include_project_specific: false)) }
...@@ -278,7 +279,7 @@ class Service < ApplicationRecord ...@@ -278,7 +279,7 @@ class Service < ApplicationRecord
active.where(instance: true), active.where(instance: true),
active.where(group_id: group_ids, inherit_from_id: nil) active.where(group_id: group_ids, inherit_from_id: nil)
]).order(Arel.sql("type ASC, array_position(#{array}::bigint[], services.group_id), instance DESC")).group_by(&:type).each do |type, records| ]).order(Arel.sql("type ASC, array_position(#{array}::bigint[], services.group_id), instance DESC")).group_by(&:type).each do |type, records|
build_from_integration(records.first, association => scope.id).save! build_from_integration(records.first, association => scope.id).save
end end
end end
......
...@@ -28,9 +28,11 @@ module Groups ...@@ -28,9 +28,11 @@ module Groups
Group.transaction do Group.transaction do
update_group_attributes update_group_attributes
ensure_ownership ensure_ownership
update_integrations
end end
post_update_hooks(@updated_project_ids) post_update_hooks(@updated_project_ids)
propagate_integrations
true true
end end
...@@ -196,6 +198,17 @@ module Groups ...@@ -196,6 +198,17 @@ module Groups
raise TransferError, result[:message] unless result[:status] == :success raise TransferError, result[:message] unless result[:status] == :success
end end
end end
def update_integrations
@group.services.inherit.delete_all
Service.create_from_active_default_integrations(@group, :group_id)
end
def propagate_integrations
@group.services.inherit.each do |integration|
PropagateIntegrationWorker.perform_async(integration.id)
end
end
end end
end end
......
...@@ -87,6 +87,8 @@ module Projects ...@@ -87,6 +87,8 @@ module Projects
# Move uploads # Move uploads
move_project_uploads(project) move_project_uploads(project)
update_integrations
project.old_path_with_namespace = @old_path project.old_path_with_namespace = @old_path
update_repository_configuration(@new_path) update_repository_configuration(@new_path)
...@@ -214,6 +216,11 @@ module Projects ...@@ -214,6 +216,11 @@ module Projects
project.shared_runners_enabled = false project.shared_runners_enabled = false
end end
end end
def update_integrations
project.services.inherit.delete_all
Service.create_from_active_default_integrations(project, :project_id)
end
end end
end end
......
---
title: Transfer a project/group to a new namespace inheriting integrations
merge_request: 48621
author:
type: changed
...@@ -238,8 +238,13 @@ RSpec.describe Groups::TransferService do ...@@ -238,8 +238,13 @@ RSpec.describe Groups::TransferService do
end end
context 'when the group is allowed to be transferred' do context 'when the group is allowed to be transferred' do
let_it_be(:new_parent_group_integration) { create(:slack_service, group: new_parent_group, project: nil, webhook: 'http://new-group.slack.com') }
before do before do
allow(PropagateIntegrationWorker).to receive(:perform_async)
create(:group_member, :owner, group: new_parent_group, user: user) create(:group_member, :owner, group: new_parent_group, user: user)
transfer_service.execute(new_parent_group) transfer_service.execute(new_parent_group)
end end
...@@ -265,6 +270,30 @@ RSpec.describe Groups::TransferService do ...@@ -265,6 +270,30 @@ RSpec.describe Groups::TransferService do
end end
end end
context 'with a group integration' do
let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
let(:new_created_integration) { Service.find_by(group: group) }
context 'with an inherited integration' do
let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com', inherit_from_id: instance_integration.id) }
it 'replaces inherited integrations', :aggregate_failures do
expect(new_created_integration.webhook).to eq(new_parent_group_integration.webhook)
expect(PropagateIntegrationWorker).to have_received(:perform_async).with(new_created_integration.id)
expect(Service.count).to eq(3)
end
end
context 'with a custom integration' do
let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com') }
it 'does not updates the integrations', :aggregate_failures do
expect { transfer_service.execute(new_parent_group) }.not_to change { group_integration.webhook }
expect(PropagateIntegrationWorker).not_to have_received(:perform_async)
end
end
end
it 'updates visibility for the group based on the parent group' do it 'updates visibility for the group based on the parent group' do
expect(group.visibility_level).to eq(new_parent_group.visibility_level) expect(group.visibility_level).to eq(new_parent_group.visibility_level)
end end
......
...@@ -7,6 +7,7 @@ RSpec.describe Projects::TransferService do ...@@ -7,6 +7,7 @@ RSpec.describe Projects::TransferService do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com') }
let(:project) { create(:project, :repository, :legacy_storage, namespace: user.namespace) } let(:project) { create(:project, :repository, :legacy_storage, namespace: user.namespace) }
subject(:execute_transfer) { described_class.new(project, user).execute(group).tap { project.reload } } subject(:execute_transfer) { described_class.new(project, user).execute(group).tap { project.reload } }
...@@ -117,6 +118,30 @@ RSpec.describe Projects::TransferService do ...@@ -117,6 +118,30 @@ RSpec.describe Projects::TransferService do
shard_name: project.repository_storage shard_name: project.repository_storage
) )
end end
context 'with a project integration' do
let_it_be_with_reload(:project) { create(:project, namespace: user.namespace) }
let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
context 'with an inherited integration' do
let_it_be(:project_integration) { create(:slack_service, project: project, webhook: 'http://project.slack.com', inherit_from_id: instance_integration.id) }
it 'replaces inherited integrations', :aggregate_failures do
execute_transfer
expect(project.slack_service.webhook).to eq(group_integration.webhook)
expect(Service.count).to eq(3)
end
end
context 'with a custom integration' do
let_it_be(:project_integration) { create(:slack_service, project: project, webhook: 'http://project.slack.com') }
it 'does not updates the integrations' do
expect { execute_transfer }.not_to change { project.slack_service.webhook }
end
end
end
end end
context 'when transfer fails' do context 'when transfer fails' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment