Commit 16c2c2fd authored by Robert Speicher's avatar Robert Speicher

Merge branch '268258-propagate-integration-to-group-descendants' into 'master'

Propagate integration to group descendants

See merge request gitlab-org/gitlab!45529
parents a66d7fa5 e8f578d0
...@@ -274,6 +274,17 @@ class Service < ApplicationRecord ...@@ -274,6 +274,17 @@ class Service < ApplicationRecord
end end
end end
def self.inherited_descendants_from_self_or_ancestors_from(integration)
inherit_from_ids =
where(type: integration.type, group: integration.group.self_and_ancestors)
.or(where(type: integration.type, instance: true)).select(:id)
from_union([
where(type: integration.type, inherit_from_id: inherit_from_ids, group: integration.group.descendants),
where(type: integration.type, inherit_from_id: inherit_from_ids, project: Project.in_namespace(integration.group.self_and_descendants))
])
end
def activated? def activated?
active active
end end
......
...@@ -5,12 +5,12 @@ module Admin ...@@ -5,12 +5,12 @@ module Admin
include PropagateService include PropagateService
def propagate def propagate
update_inherited_integrations
if integration.instance? if integration.instance?
update_inherited_integrations
create_integration_for_groups_without_integration if Feature.enabled?(:group_level_integrations) create_integration_for_groups_without_integration if Feature.enabled?(:group_level_integrations)
create_integration_for_projects_without_integration create_integration_for_projects_without_integration
else else
update_inherited_descendant_integrations
create_integration_for_groups_without_integration_belonging_to_group create_integration_for_groups_without_integration_belonging_to_group
create_integration_for_projects_without_integration_belonging_to_group create_integration_for_projects_without_integration_belonging_to_group
end end
...@@ -18,34 +18,39 @@ module Admin ...@@ -18,34 +18,39 @@ module Admin
private private
# rubocop: disable Cop/InBatches
def update_inherited_integrations def update_inherited_integrations
Service.by_type(integration.type).inherit_from_id(integration.id).each_batch(of: BATCH_SIZE) do |services| propagate_integrations(
min_id, max_id = services.pick("MIN(services.id), MAX(services.id)") Service.by_type(integration.type).inherit_from_id(integration.id),
PropagateIntegrationInheritWorker.perform_async(integration.id, min_id, max_id) PropagateIntegrationInheritWorker
)
end end
def update_inherited_descendant_integrations
propagate_integrations(
Service.inherited_descendants_from_self_or_ancestors_from(integration),
PropagateIntegrationInheritDescendantWorker
)
end end
# rubocop: enable Cop/InBatches
def create_integration_for_groups_without_integration def create_integration_for_groups_without_integration
Group.without_integration(integration).each_batch(of: BATCH_SIZE) do |groups| propagate_integrations(
min_id, max_id = groups.pick("MIN(namespaces.id), MAX(namespaces.id)") Group.without_integration(integration),
PropagateIntegrationGroupWorker.perform_async(integration.id, min_id, max_id) PropagateIntegrationGroupWorker
end )
end end
def create_integration_for_groups_without_integration_belonging_to_group def create_integration_for_groups_without_integration_belonging_to_group
integration.group.descendants.without_integration(integration).each_batch(of: BATCH_SIZE) do |groups| propagate_integrations(
min_id, max_id = groups.pick("MIN(namespaces.id), MAX(namespaces.id)") integration.group.descendants.without_integration(integration),
PropagateIntegrationGroupWorker.perform_async(integration.id, min_id, max_id) PropagateIntegrationGroupWorker
end )
end end
def create_integration_for_projects_without_integration_belonging_to_group def create_integration_for_projects_without_integration_belonging_to_group
Project.without_integration(integration).in_namespace(integration.group.self_and_descendants).each_batch(of: BATCH_SIZE) do |projects| propagate_integrations(
min_id, max_id = projects.pick("MIN(projects.id), MAX(projects.id)") Project.without_integration(integration).in_namespace(integration.group.self_and_descendants),
PropagateIntegrationProjectWorker.perform_async(integration.id, min_id, max_id) PropagateIntegrationProjectWorker
end )
end end
end end
end end
...@@ -47,7 +47,7 @@ class BulkCreateIntegrationService ...@@ -47,7 +47,7 @@ class BulkCreateIntegrationService
if integration.template? if integration.template?
integration.to_service_hash integration.to_service_hash
else else
integration.to_service_hash.tap { |json| json['inherit_from_id'] = integration.id } integration.to_service_hash.tap { |json| json['inherit_from_id'] = integration.inherit_from_id || integration.id }
end end
end end
......
...@@ -23,7 +23,7 @@ class BulkUpdateIntegrationService ...@@ -23,7 +23,7 @@ class BulkUpdateIntegrationService
attr_reader :integration, :batch attr_reader :integration, :batch
def service_hash def service_hash
integration.to_service_hash.tap { |json| json['inherit_from_id'] = integration.id } integration.to_service_hash.tap { |json| json['inherit_from_id'] = integration.inherit_from_id || integration.id }
end end
def data_fields_hash def data_fields_hash
......
...@@ -21,9 +21,16 @@ module Admin ...@@ -21,9 +21,16 @@ module Admin
attr_reader :integration attr_reader :integration
def create_integration_for_projects_without_integration def create_integration_for_projects_without_integration
Project.without_integration(integration).each_batch(of: BATCH_SIZE) do |projects| propagate_integrations(
min_id, max_id = projects.pick("MIN(projects.id), MAX(projects.id)") Project.without_integration(integration),
PropagateIntegrationProjectWorker.perform_async(integration.id, min_id, max_id) PropagateIntegrationProjectWorker
)
end
def propagate_integrations(relation, worker_class)
relation.each_batch(of: BATCH_SIZE) do |records|
min_id, max_id = records.pick("MIN(#{relation.table_name}.id), MAX(#{relation.table_name}.id)")
worker_class.perform_async(integration.id, min_id, max_id)
end end
end end
end end
......
...@@ -1847,6 +1847,14 @@ ...@@ -1847,6 +1847,14 @@
:weight: 1 :weight: 1
:idempotent: true :idempotent: true
:tags: [] :tags: []
- :name: propagate_integration_inherit_descendant
:feature_category: :integrations
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: propagate_integration_project - :name: propagate_integration_project
:feature_category: :integrations :feature_category: :integrations
:has_external_dependencies: :has_external_dependencies:
......
# frozen_string_literal: true
class PropagateIntegrationInheritDescendantWorker
include ApplicationWorker
feature_category :integrations
idempotent!
# rubocop: disable CodeReuse/ActiveRecord
def perform(integration_id, min_id, max_id)
integration = Service.find_by_id(integration_id)
return unless integration
batch = Service.inherited_descendants_from_self_or_ancestors_from(integration).where(id: min_id..max_id)
BulkUpdateIntegrationService.new(integration, batch).execute
end
# rubocop: enable CodeReuse/ActiveRecord
end
...@@ -11,9 +11,9 @@ class PropagateIntegrationInheritWorker ...@@ -11,9 +11,9 @@ class PropagateIntegrationInheritWorker
integration = Service.find_by_id(integration_id) integration = Service.find_by_id(integration_id)
return unless integration return unless integration
services = Service.where(id: min_id..max_id).by_type(integration.type).inherit_from_id(integration.id) batch = Service.where(id: min_id..max_id).by_type(integration.type).inherit_from_id(integration.id)
BulkUpdateIntegrationService.new(integration, services).execute BulkUpdateIntegrationService.new(integration, batch).execute
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
...@@ -254,6 +254,8 @@ ...@@ -254,6 +254,8 @@
- 1 - 1
- - propagate_integration_inherit - - propagate_integration_inherit
- 1 - 1
- - propagate_integration_inherit_descendant
- 1
- - propagate_integration_project - - propagate_integration_project
- 1 - 1
- - propagate_service_template - - propagate_service_template
......
...@@ -599,6 +599,23 @@ RSpec.describe Service do ...@@ -599,6 +599,23 @@ RSpec.describe Service do
end end
end end
describe '.inherited_descendants_from_self_or_ancestors_from' do
let_it_be(:subgroup1) { create(:group, parent: group) }
let_it_be(:subgroup2) { create(:group, parent: group) }
let_it_be(:project1) { create(:project, group: subgroup1) }
let_it_be(:project2) { create(:project, group: subgroup2) }
let_it_be(:group_integration) { create(:prometheus_service, group: group, project: nil) }
let_it_be(:subgroup_integration1) { create(:prometheus_service, group: subgroup1, project: nil, inherit_from_id: group_integration.id) }
let_it_be(:subgroup_integration2) { create(:prometheus_service, group: subgroup2, project: nil) }
let_it_be(:project_integration1) { create(:prometheus_service, group: nil, project: project1, inherit_from_id: group_integration.id) }
let_it_be(:project_integration2) { create(:prometheus_service, group: nil, project: project2, inherit_from_id: subgroup_integration2.id) }
it 'returns the groups and projects inheriting from integration ancestors', :aggregate_failures do
expect(described_class.inherited_descendants_from_self_or_ancestors_from(group_integration)).to eq([subgroup_integration1, project_integration1])
expect(described_class.inherited_descendants_from_self_or_ancestors_from(subgroup_integration2)).to eq([project_integration2])
end
end
describe "{property}_changed?" do describe "{property}_changed?" do
let(:service) do let(:service) do
BambooService.create( BambooService.create(
......
...@@ -67,7 +67,7 @@ RSpec.describe Admin::PropagateIntegrationService do ...@@ -67,7 +67,7 @@ RSpec.describe Admin::PropagateIntegrationService do
end end
end end
context 'with a group without integration' do context 'with a subgroup without integration' do
let(:subgroup) { create(:group, parent: group) } let(:subgroup) { create(:group, parent: group) }
it 'calls to PropagateIntegrationGroupWorker' do it 'calls to PropagateIntegrationGroupWorker' do
...@@ -77,6 +77,18 @@ RSpec.describe Admin::PropagateIntegrationService do ...@@ -77,6 +77,18 @@ RSpec.describe Admin::PropagateIntegrationService do
described_class.propagate(group_integration) described_class.propagate(group_integration)
end end
end end
context 'with a subgroup with integration' do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_integration) { create(:jira_service, group: subgroup, project: nil, inherit_from_id: group_integration.id) }
it 'calls to PropagateIntegrationInheritDescendantWorker' do
expect(PropagateIntegrationInheritDescendantWorker).to receive(:perform_async)
.with(group_integration.id, subgroup_integration.id, subgroup_integration.id)
described_class.propagate(group_integration)
end
end
end end
end end
end end
...@@ -9,9 +9,9 @@ RSpec.describe BulkCreateIntegrationService do ...@@ -9,9 +9,9 @@ RSpec.describe BulkCreateIntegrationService do
stub_jira_service_test stub_jira_service_test
end end
let_it_be(:instance_integration) { create(:jira_service, :instance) }
let_it_be(:template_integration) { create(:jira_service, :template) }
let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] } let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] }
let!(:instance_integration) { create(:jira_service, :instance) }
let!(:template_integration) { create(:jira_service, :template) }
shared_examples 'creates integration from batch ids' do shared_examples 'creates integration from batch ids' do
it 'updates the inherited integrations' do it 'updates the inherited integrations' do
...@@ -37,7 +37,7 @@ RSpec.describe BulkCreateIntegrationService do ...@@ -37,7 +37,7 @@ RSpec.describe BulkCreateIntegrationService do
it 'updates inherit_from_id attributes' do it 'updates inherit_from_id attributes' do
described_class.new(integration, batch, association).execute described_class.new(integration, batch, association).execute
expect(created_integration.reload.inherit_from_id).to eq(integration.id) expect(created_integration.reload.inherit_from_id).to eq(inherit_from_id)
end end
end end
...@@ -79,6 +79,7 @@ RSpec.describe BulkCreateIntegrationService do ...@@ -79,6 +79,7 @@ RSpec.describe BulkCreateIntegrationService do
context 'with an instance-level integration' do context 'with an instance-level integration' do
let(:integration) { instance_integration } let(:integration) { instance_integration }
let(:inherit_from_id) { integration.id }
context 'with a project association' do context 'with a project association' do
let!(:project) { create(:project) } let!(:project) { create(:project) }
...@@ -100,13 +101,26 @@ RSpec.describe BulkCreateIntegrationService do ...@@ -100,13 +101,26 @@ RSpec.describe BulkCreateIntegrationService do
end end
context 'with a group association' do context 'with a group association' do
let!(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:created_integration) { Service.find_by(group: group) } let(:created_integration) { Service.find_by(group: group) }
let(:batch) { Group.all } let(:batch) { Group.all }
let(:association) { 'group' } let(:association) { 'group' }
it_behaves_like 'creates integration from batch ids' it_behaves_like 'creates integration from batch ids'
it_behaves_like 'updates inherit_from_id' it_behaves_like 'updates inherit_from_id'
context 'with a subgroup association' do
let_it_be(:group_integration) { create(:jira_service, group: group, project: nil, inherit_from_id: instance_integration.id) }
let_it_be(:subgroup) { create(:group, parent: group) }
let(:integration) { group_integration }
let(:created_integration) { Service.find_by(group: subgroup) }
let(:batch) { Group.all }
let(:association) { 'group' }
let(:inherit_from_id) { instance_integration.id }
it_behaves_like 'creates integration from batch ids'
it_behaves_like 'updates inherit_from_id'
end
end end
end end
...@@ -118,6 +132,7 @@ RSpec.describe BulkCreateIntegrationService do ...@@ -118,6 +132,7 @@ RSpec.describe BulkCreateIntegrationService do
let(:created_integration) { project.jira_service } let(:created_integration) { project.jira_service }
let(:batch) { Project.all } let(:batch) { Project.all }
let(:association) { 'project' } let(:association) { 'project' }
let(:inherit_from_id) { integration.id }
it_behaves_like 'creates integration from batch ids' it_behaves_like 'creates integration from batch ids'
it_behaves_like 'updates project callbacks' it_behaves_like 'updates project callbacks'
......
...@@ -5,14 +5,28 @@ require 'spec_helper' ...@@ -5,14 +5,28 @@ require 'spec_helper'
RSpec.describe BulkUpdateIntegrationService do RSpec.describe BulkUpdateIntegrationService do
include JiraServiceHelper include JiraServiceHelper
before do before_all do
stub_jira_service_test stub_jira_service_test
end end
let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] } let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] }
let!(:instance_integration) do
let_it_be(:group) { create(:group) }
let_it_be(:group_integration) do
JiraService.create!(
group: group,
active: true,
push_events: true,
url: 'http://update-jira.instance.com',
username: 'user',
password: 'secret'
)
end
let_it_be(:subgroup_integration) do
JiraService.create!( JiraService.create!(
instance: true, inherit_from_id: group_integration.id,
group: create(:group, parent: group),
active: true, active: true,
push_events: true, push_events: true,
url: 'http://update-jira.instance.com', url: 'http://update-jira.instance.com',
...@@ -21,10 +35,9 @@ RSpec.describe BulkUpdateIntegrationService do ...@@ -21,10 +35,9 @@ RSpec.describe BulkUpdateIntegrationService do
) )
end end
let!(:integration) do let_it_be(:integration) do
JiraService.create!( JiraService.create!(
project: create(:project), project: create(:project),
inherit_from_id: instance_integration.id,
instance: false, instance: false,
active: true, active: true,
push_events: false, push_events: false,
...@@ -36,21 +49,21 @@ RSpec.describe BulkUpdateIntegrationService do ...@@ -36,21 +49,21 @@ RSpec.describe BulkUpdateIntegrationService do
context 'with inherited integration' do context 'with inherited integration' do
it 'updates the integration' do it 'updates the integration' do
described_class.new(instance_integration, Service.inherit_from_id(instance_integration.id)).execute described_class.new(subgroup_integration, Service.where.not(project: nil)).execute
expect(integration.reload.inherit_from_id).to eq(instance_integration.id) expect(integration.reload.inherit_from_id).to eq(group_integration.id)
expect(integration.attributes.except(*excluded_attributes)) expect(integration.attributes.except(*excluded_attributes))
.to eq(instance_integration.attributes.except(*excluded_attributes)) .to eq(subgroup_integration.attributes.except(*excluded_attributes))
end end
context 'with integration with data fields' do context 'with integration with data fields' do
let(:excluded_attributes) { %w[id service_id created_at updated_at] } let(:excluded_attributes) { %w[id service_id created_at updated_at] }
it 'updates the data fields from the integration' do it 'updates the data fields from the integration' do
described_class.new(instance_integration, Service.inherit_from_id(instance_integration.id)).execute described_class.new(subgroup_integration, Service.where.not(project: nil)).execute
expect(integration.reload.data_fields.attributes.except(*excluded_attributes)) expect(integration.reload.data_fields.attributes.except(*excluded_attributes))
.to eq(instance_integration.data_fields.attributes.except(*excluded_attributes)) .to eq(subgroup_integration.data_fields.attributes.except(*excluded_attributes))
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe PropagateIntegrationInheritDescendantWorker do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:group_integration) { create(:redmine_service, group: group, project: nil) }
let_it_be(:subgroup_integration) { create(:redmine_service, group: subgroup, project: nil, inherit_from_id: group_integration.id) }
it_behaves_like 'an idempotent worker' do
let(:job_args) { [group_integration.id, subgroup_integration.id, subgroup_integration.id] }
it 'calls to BulkUpdateIntegrationService' do
expect(BulkUpdateIntegrationService).to receive(:new)
.with(group_integration, match_array(subgroup_integration)).twice
.and_return(double(execute: nil))
subject
end
end
context 'with an invalid integration id' do
it 'returns without failure' do
expect(BulkUpdateIntegrationService).not_to receive(:new)
subject.perform(0, subgroup_integration.id, subgroup_integration.id)
end
end
end
...@@ -12,7 +12,7 @@ RSpec.describe PropagateIntegrationInheritWorker do ...@@ -12,7 +12,7 @@ RSpec.describe PropagateIntegrationInheritWorker do
it_behaves_like 'an idempotent worker' do it_behaves_like 'an idempotent worker' do
let(:job_args) { [integration.id, integration1.id, integration3.id] } let(:job_args) { [integration.id, integration1.id, integration3.id] }
it 'calls to BulkCreateIntegrationService' do it 'calls to BulkUpdateIntegrationService' do
expect(BulkUpdateIntegrationService).to receive(:new) expect(BulkUpdateIntegrationService).to receive(:new)
.with(integration, match_array(integration1)).twice .with(integration, match_array(integration1)).twice
.and_return(double(execute: nil)) .and_return(double(execute: nil))
......
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