From 5d7f48ba36213901fb8bfe5265b7ebc0b87fe466 Mon Sep 17 00:00:00 2001 From: Pedro Pombeiro Date: Fri, 29 Jul 2022 17:27:30 +0200 Subject: [PATCH] Create service to delete runners in bulk Changelog: added --- .../ci/runners/bulk_delete_runners_service.rb | 36 +++++++++ .../bulk_delete_runners_service_spec.rb | 79 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 app/services/ci/runners/bulk_delete_runners_service.rb create mode 100644 spec/services/ci/runners/bulk_delete_runners_service_spec.rb diff --git a/app/services/ci/runners/bulk_delete_runners_service.rb b/app/services/ci/runners/bulk_delete_runners_service.rb new file mode 100644 index 00000000000000..7af8994c83d963 --- /dev/null +++ b/app/services/ci/runners/bulk_delete_runners_service.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Ci + module Runners + class BulkDeleteRunnersService + attr_reader :runners + + RUNNER_LIMIT = 50 + + # @param runners [Array] the runners to unregister/destroy + def initialize(runners:) + @runners = runners + end + + def execute + if @runners + # Delete a few runners immediately + return delete_runners + end + + { deleted_count: 0, deleted_ids: [] } + end + + private + + def delete_runners + # rubocop:disable CodeReuse/ActiveRecord + runners_to_be_deleted = Ci::Runner.where(id: @runners).limit(RUNNER_LIMIT) + # rubocop:enable CodeReuse/ActiveRecord + deleted_ids = runners_to_be_deleted.destroy_all.map(&:id) # rubocop: disable Cop/DestroyAll + + { deleted_count: deleted_ids.count, deleted_ids: deleted_ids } + end + end + end +end diff --git a/spec/services/ci/runners/bulk_delete_runners_service_spec.rb b/spec/services/ci/runners/bulk_delete_runners_service_spec.rb new file mode 100644 index 00000000000000..3122e861bea5de --- /dev/null +++ b/spec/services/ci/runners/bulk_delete_runners_service_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Ci::Runners::BulkDeleteRunnersService, '#execute' do + subject { described_class.new(**service_args).execute } + + let(:service_args) { { runners: runners_arg } } + let(:runners_arg) { } + + context 'with runners specified' do + let!(:instance_runner) { create(:ci_runner) } + let!(:group_runner) { create(:ci_runner, :group) } + let!(:project_runner) { create(:ci_runner, :project) } + + shared_examples 'a service deleting runners in bulk' do + it 'destroys runners', :aggregate_failures do + expect { subject }.to change { Ci::Runner.count }.by(-2) + + is_expected.to eq({ deleted_count: 2, deleted_ids: [instance_runner.id, project_runner.id] }) + expect(instance_runner[:errors]).to be_nil + expect(project_runner[:errors]).to be_nil + expect { project_runner.runner_projects.first.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { group_runner.reload }.not_to raise_error + expect { instance_runner.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { project_runner.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + context 'with some runners already deleted' do + before do + instance_runner.destroy! + end + + let(:runners_arg) { [instance_runner.id, project_runner.id] } + + it 'destroys runners and returns only deleted runners', :aggregate_failures do + expect { subject }.to change { Ci::Runner.count }.by(-1) + + is_expected.to eq({ deleted_count: 1, deleted_ids: [project_runner.id] }) + expect(instance_runner[:errors]).to be_nil + expect(project_runner[:errors]).to be_nil + expect { project_runner.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with too many runners specified' do + before do + stub_const("#{described_class}::RUNNER_LIMIT", 1) + end + + it 'deletes only first RUNNER_LIMIT runners' do + expect { subject }.to change { Ci::Runner.count }.by(-1) + + is_expected.to eq({ deleted_count: 1, deleted_ids: [instance_runner.id] }) + end + end + end + + context 'with runners specified as relation' do + let(:runners_arg) { Ci::Runner.not_group_type } + + include_examples 'a service deleting runners in bulk' + end + + context 'with runners specified as array of IDs' do + let(:runners_arg) { Ci::Runner.not_group_type.ids } + + include_examples 'a service deleting runners in bulk' + end + + context 'with no arguments specified' do + let(:runners_arg) { nil } + + it 'returns 0 deleted runners' do + is_expected.to eq({ deleted_count: 0, deleted_ids: [] }) + end + end + end +end -- GitLab