Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/project_authorizations/changes.rb')
-rw-r--r--app/models/project_authorizations/changes.rb143
1 files changed, 143 insertions, 0 deletions
diff --git a/app/models/project_authorizations/changes.rb b/app/models/project_authorizations/changes.rb
new file mode 100644
index 00000000000..1d717950c1c
--- /dev/null
+++ b/app/models/project_authorizations/changes.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+module ProjectAuthorizations
+ # How to use this class
+ # authorizations_to_add:
+ # Rows to insert in the form `[{ user_id: user_id, project_id: project_id, access_level: access_level}, ...]
+ #
+ # ProjectAuthorizations::Changes.new do |changes|
+ # changes.add(authorizations_to_add)
+ # changes.remove_users_in_project(project, user_ids)
+ # changes.remove_projects_for_user(user, project_ids)
+ # end.apply!
+ class Changes
+ attr_reader :projects_to_remove, :users_to_remove, :authorizations_to_add
+
+ BATCH_SIZE = 1000
+ SLEEP_DELAY = 0.1
+
+ def initialize
+ @authorizations_to_add = []
+ @affected_project_ids = Set.new
+ yield self
+ end
+
+ def add(authorizations_to_add)
+ @authorizations_to_add += authorizations_to_add
+ end
+
+ def remove_users_in_project(project, user_ids)
+ @users_to_remove = { user_ids: user_ids, scope: project }
+ end
+
+ def remove_projects_for_user(user, project_ids)
+ @projects_to_remove = { project_ids: project_ids, scope: user }
+ end
+
+ def apply!
+ delete_authorizations_for_user if should_delete_authorizations_for_user?
+ delete_authorizations_for_project if should_delete_authorizations_for_project?
+ add_authorizations if should_add_authorization?
+
+ publish_events
+ end
+
+ private
+
+ def should_add_authorization?
+ authorizations_to_add.present?
+ end
+
+ def should_delete_authorizations_for_user?
+ user && project_ids.present?
+ end
+
+ def should_delete_authorizations_for_project?
+ project && user_ids.present?
+ end
+
+ def add_authorizations
+ insert_all_in_batches(authorizations_to_add)
+ @affected_project_ids += authorizations_to_add.pluck(:project_id)
+ end
+
+ def delete_authorizations_for_user
+ delete_all_in_batches(resource: user,
+ ids_to_remove: project_ids,
+ column_name_of_ids_to_remove: :project_id)
+ @affected_project_ids += project_ids
+ end
+
+ def delete_authorizations_for_project
+ delete_all_in_batches(resource: project,
+ ids_to_remove: user_ids,
+ column_name_of_ids_to_remove: :user_id)
+ @affected_project_ids << project.id
+ end
+
+ def delete_all_in_batches(resource:, ids_to_remove:, column_name_of_ids_to_remove:)
+ add_delay = add_delay_between_batches?(entire_size: ids_to_remove.size, batch_size: BATCH_SIZE)
+ log_details(entire_size: ids_to_remove.size, batch_size: BATCH_SIZE) if add_delay
+
+ ids_to_remove.each_slice(BATCH_SIZE) do |ids_batch|
+ resource.project_authorizations.where(column_name_of_ids_to_remove => ids_batch).delete_all
+ perform_delay if add_delay
+ end
+ end
+
+ def insert_all_in_batches(attributes)
+ add_delay = add_delay_between_batches?(entire_size: attributes.size, batch_size: BATCH_SIZE)
+ log_details(entire_size: attributes.size, batch_size: BATCH_SIZE) if add_delay
+
+ attributes.each_slice(BATCH_SIZE) do |attributes_batch|
+ ProjectAuthorization.insert_all(attributes_batch)
+ perform_delay if add_delay
+ end
+ end
+
+ def add_delay_between_batches?(entire_size:, batch_size:)
+ # The reason for adding a delay is to give the replica database enough time to
+ # catch up with the primary when large batches of records are being added/removed.
+ # Hence, we add a delay only if the GitLab installation has a replica database configured.
+ entire_size > batch_size &&
+ !::Gitlab::Database::LoadBalancing.primary_only?
+ end
+
+ def log_details(entire_size:, batch_size:)
+ Gitlab::AppLogger.info(
+ entire_size: entire_size,
+ total_delay: (entire_size / batch_size.to_f).ceil * SLEEP_DELAY,
+ message: 'Project authorizations refresh performed with delay',
+ **Gitlab::ApplicationContext.current
+ )
+ end
+
+ def perform_delay
+ sleep(SLEEP_DELAY)
+ end
+
+ def user
+ projects_to_remove&.[](:scope)
+ end
+
+ def project_ids
+ projects_to_remove&.[](:project_ids)
+ end
+
+ def project
+ users_to_remove&.[](:scope)
+ end
+
+ def user_ids
+ users_to_remove&.[](:user_ids)
+ end
+
+ def publish_events
+ @affected_project_ids.each do |project_id|
+ ::Gitlab::EventStore.publish(
+ ::ProjectAuthorizations::AuthorizationsChangedEvent.new(data: { project_id: project_id })
+ )
+ end
+ end
+ end
+end