File: create_service.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (121 lines) | stat: -rw-r--r-- 4,206 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# frozen_string_literal: true

# Exclusions are custom settings at the group or project level used to selectively deactivate an instance integration
# https://gitlab.com/gitlab-org/gitlab/-/issues/454372
module Integrations
  module Exclusions
    class CreateService < BaseService
      MAX_PROJECTS = 100
      MAX_GROUPS = 100

      def execute
        result = validate
        return result if result.present?

        return ServiceResponse.error(message: 'project limit exceeded') if projects_over_limit?
        return ServiceResponse.error(message: 'group limit exceeded') if groups_over_limit?

        project_integrations = create_project_integrations
        group_integrations = create_group_integrations
        ServiceResponse.success(payload: group_integrations + project_integrations)
      end

      private

      def create_project_integrations
        projects = filtered_projects
        return Integration.none unless projects.present?

        integration_attrs = projects.map do |project|
          {
            project_id: project.id,
            type_new: integration_type,
            active: false,
            inherit_from_id: nil
          }
        end

        result = Integration.upsert_all(integration_attrs, unique_by: [:project_id, :type_new])
        Integration.id_in(result.rows.flatten)
      end

      def create_group_integrations
        groups = filtered_groups
        return Integration.none unless groups.present?

        integrations_for_groups = integration_model.for_group(groups)
        existing_group_ids = integrations_for_groups.map(&:group_id).to_set
        groups_missing_integrations = groups.reject do |g|
          existing_group_ids.include?(g.id)
        end

        integrations_to_update = integrations_for_groups.select do |integration|
          integration.inherit_from_id.present? || integration.activated?
        end
        integration_ids_to_update = integrations_to_update.map(&:id)
        integration_model.id_in(integration_ids_to_update).update_all(inherit_from_id: nil, active: false)

        integration_attrs = groups_missing_integrations.map do |g|
          {
            group_id: g.id,
            active: false,
            inherit_from_id: nil,
            type_new: integration_type
          }
        end

        created_group_integration_ids = []
        if integration_attrs.present?
          created_group_integration_ids = Integration.insert_all(integration_attrs,
            returning: :id).rows.flatten
        end

        new_exclusions = Integration.id_in(integration_ids_to_update + created_group_integration_ids)
        new_exclusions.each do |integration|
          PropagateIntegrationWorker.perform_async(integration.id)
        end
        new_exclusions
      end

      # Exclusions for groups should propagate to subgroup children
      # Skip creating integrations for subgroups and projects that would already be deactivated by an ancestor
      # integration.
      # Also skip for projects and groups that would be deactivated by creating an integration for another group in the
      # same call to #execute.
      def filtered_groups
        group_ids = groups.map(&:id) + ancestor_integration_group_ids
        groups.reject do |g|
          g.ancestor_ids.intersect?(group_ids)
        end
      end
      strong_memoize_attr :filtered_groups

      def filtered_projects
        filtered_group_ids = filtered_groups.map(&:id) + ancestor_integration_group_ids

        projects.reject do |p|
          p&.group&.self_and_ancestor_ids&.intersect?(filtered_group_ids)
        end
      end
      strong_memoize_attr :filtered_projects

      def ancestor_integration_group_ids
        integration_model
          .with_custom_settings
          .for_group(
            (groups.flat_map(&:traversal_ids) + projects.flat_map { |p| p&.group&.traversal_ids }).compact.uniq
          ).limit(MAX_GROUPS + MAX_PROJECTS)
          .pluck_group_id
      end
      strong_memoize_attr :ancestor_integration_group_ids

      def projects_over_limit?
        projects.size > MAX_PROJECTS
      end

      def groups_over_limit?
        groups.size > MAX_GROUPS
      end
    end
  end
end