diff options
Diffstat (limited to 'app/services/environments/schedule_to_delete_review_apps_service.rb')
-rw-r--r-- | app/services/environments/schedule_to_delete_review_apps_service.rb | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/app/services/environments/schedule_to_delete_review_apps_service.rb b/app/services/environments/schedule_to_delete_review_apps_service.rb new file mode 100644 index 00000000000..b3b86689748 --- /dev/null +++ b/app/services/environments/schedule_to_delete_review_apps_service.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module Environments + class ScheduleToDeleteReviewAppsService < ::BaseService + include ::Gitlab::ExclusiveLeaseHelpers + + EXCLUSIVE_LOCK_KEY_BASE = 'environments:delete_review_apps:lock' + LOCK_TIMEOUT = 2.minutes + + def execute + if validation_error = validate + return validation_error + end + + mark_deletable_environments + end + + private + + def key + "#{EXCLUSIVE_LOCK_KEY_BASE}:#{project.id}" + end + + def dry_run? + return true if params[:dry_run].nil? + + params[:dry_run] + end + + def validate + return if can?(current_user, :destroy_environment, project) + + Result.new(error_message: "You do not have permission to destroy environments in this project", status: :unauthorized) + end + + def mark_deletable_environments + in_lock(key, ttl: LOCK_TIMEOUT, retries: 1) do + unsafe_mark_deletable_environments + end + + rescue FailedToObtainLockError + Result.new(error_message: "Another process is already processing a delete request. Please retry later.", status: :conflict) + end + + def unsafe_mark_deletable_environments + result = Result.new + environments = project.environments + .not_scheduled_for_deletion + .stopped_review_apps(params[:before], params[:limit]) + + # Check if the actor has write permission to a potentially-protected environment. + deletable, failed = *environments.partition { |env| current_user.can?(:destroy_environment, env) } + + if deletable.any? && failed.empty? + mark_for_deletion(deletable) unless dry_run? + result.set_status(:ok) + result.set_scheduled_entries(deletable) + else + result.set_status( + :bad_request, + error_message: "Failed to authorize deletions for some or all of the environments. Ask someone with more permissions to delete the environments." + ) + + result.set_unprocessable_entries(failed) + end + + result + end + + def mark_for_deletion(deletable_environments) + Environment.for_id(deletable_environments).schedule_to_delete + end + + class Result + attr_accessor :scheduled_entries, :unprocessable_entries, :error_message, :status + + def initialize(scheduled_entries: [], unprocessable_entries: [], error_message: nil, status: nil) + self.scheduled_entries = scheduled_entries + self.unprocessable_entries = unprocessable_entries + self.error_message = error_message + self.status = status + end + + def success? + status == :ok + end + + def set_status(status, error_message: nil) + self.status = status + self.error_message = error_message + end + + def set_scheduled_entries(entries) + self.scheduled_entries = entries + end + + def set_unprocessable_entries(entries) + self.unprocessable_entries = entries + end + end + end +end |