Commit 1855eb3a authored by Aleksei Lipniagov's avatar Aleksei Lipniagov

Merge branch 'mc_rocha-capture-failed-dast-schedule-335662' into 'master'

Capture failed dast schedules

See merge request gitlab-org/gitlab!72024
parents 51a7822d faaab85e
...@@ -55,12 +55,12 @@ class Dast::ProfileSchedule < ApplicationRecord ...@@ -55,12 +55,12 @@ class Dast::ProfileSchedule < ApplicationRecord
Ability.allowed?(owner, :create_on_demand_dast_scan, project) Ability.allowed?(owner, :create_on_demand_dast_scan, project)
end end
private
def deactivate! def deactivate!
update!(active: false) update!(active: false)
end end
private
def cron_timezone def cron_timezone
Time.zone.name Time.zone.name
end end
......
...@@ -18,13 +18,10 @@ module AppSec ...@@ -18,13 +18,10 @@ module AppSec
dast_runnable_schedules.find_in_batches do |schedules| dast_runnable_schedules.find_in_batches do |schedules|
schedules.each do |schedule| schedules.each do |schedule|
with_context(project: schedule.project, user: schedule.owner) do if schedule.owner_valid?
schedule.schedule_next_run! execute_schedule(schedule)
else
response = service(schedule).execute schedule.deactivate!
if response.error?
logger.info(structured_payload(message: response.message))
end
end end
end end
end end
...@@ -46,6 +43,17 @@ module AppSec ...@@ -46,6 +43,17 @@ module AppSec
} }
) )
end end
def execute_schedule(schedule)
with_context(project: schedule.project, user: schedule.owner) do
schedule.schedule_next_run!
response = service(schedule).execute
if response.error?
logger.info(structured_payload(message: response.message))
end
end
end
end end
end end
end end
...@@ -5,7 +5,9 @@ RSpec.describe AppSec::Dast::ProfileScheduleWorker do ...@@ -5,7 +5,9 @@ RSpec.describe AppSec::Dast::ProfileScheduleWorker do
include ExclusiveLeaseHelpers include ExclusiveLeaseHelpers
let_it_be(:plan_limits) { create(:plan_limits, :default_plan) } let_it_be(:plan_limits) { create(:plan_limits, :default_plan) }
let_it_be(:schedule) { create(:dast_profile_schedule) } let_it_be(:owner) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:schedule) { create(:dast_profile_schedule, owner: owner, project: project) }
let(:worker) { described_class.new } let(:worker) { described_class.new }
let(:logger) { worker.send(:logger) } let(:logger) { worker.send(:logger) }
...@@ -13,6 +15,8 @@ RSpec.describe AppSec::Dast::ProfileScheduleWorker do ...@@ -13,6 +15,8 @@ RSpec.describe AppSec::Dast::ProfileScheduleWorker do
let(:service_result) { ServiceResponse.success } let(:service_result) { ServiceResponse.success }
before do before do
project.add_developer(owner)
allow(::AppSec::Dast::Scans::CreateService) allow(::AppSec::Dast::Scans::CreateService)
.to receive(:new) .to receive(:new)
.and_return(service) .and_return(service)
...@@ -33,87 +37,128 @@ RSpec.describe AppSec::Dast::ProfileScheduleWorker do ...@@ -33,87 +37,128 @@ RSpec.describe AppSec::Dast::ProfileScheduleWorker do
subject subject
end end
end end
context 'when feature is licensed' do
context 'when multiple schedules exists' do
before do before do
schedule.update_column(:next_run_at, 1.minute.from_now) stub_licensed_features(security_on_demand_scans: true)
end end
def record_preloaded_queries context 'when multiple schedules exists' do
recorder = ActiveRecord::QueryRecorder.new { subject } before do
recorder.data.values.flat_map {|v| v[:occurrences]}.select do |query| schedule.update_column(:next_run_at, 1.minute.from_now)
['FROM "projects"', 'FROM "users"', 'FROM "dast_profile"', 'FROM "dast_profile_schedule"'].any? do |s| end
query.include?(s)
def record_preloaded_queries
recorder = ActiveRecord::QueryRecorder.new { subject }
recorder.data.values.flat_map { |v| v[:occurrences] }.select do |query|
['FROM "projects"', 'FROM "users"', 'FROM "dast_profile"', 'FROM "dast_profile_schedule"'].any? do |s|
query.include?(s)
end
end end
end end
end
it 'preloads configuration, project and owner to avoid N+1 queries' do it 'preloads configuration, project and owner to avoid N+1 queries' do
expected_count = record_preloaded_queries.count expected_count = record_preloaded_queries.count
travel_to(30.minutes.ago) { create_list(:dast_profile_schedule, 5) } travel_to(30.minutes.ago) { create_list(:dast_profile_schedule, 5) }
actual_count = record_preloaded_queries.count actual_count = record_preloaded_queries.count
expect(actual_count).to eq(expected_count) expect(actual_count).to eq(expected_count)
end end
end
context 'when schedule exists' do context 'when all of the schedule owners are invalid' do
before do before do
schedule.update_column(:next_run_at, 1.minute.ago) travel_to(30.minutes.ago) { create_list(:dast_profile_schedule, 5, owner: nil, active: true) }
end end
it 'executes the rule schedule service' do it 'sets active to false' do
expect_next_found_instance_of(::Dast::ProfileSchedule) do |schedule| expect { subject }.to change { Dast::ProfileSchedule.where(active: false).count }.to(5)
expect(schedule).to receive(:schedule_next_run!) end
end end
expect(service).to receive(:execute) context 'when some of the schedule owners are invalid' do
before do
travel_to(30.minutes.ago) do
create_list(:dast_profile_schedule, 2, owner: nil, active: true)
create_list(:dast_profile_schedule, 3, owner: owner, active: true, project: project)
end
end
subject it 'sets active to false' do
expect(service).to receive(:execute)
subject
expect(Dast::ProfileSchedule.where(active: false).count).to eq(2)
end
end
end end
end
context 'when service returns an error' do context 'when schedule exists' do
before do before do
schedule.update_column(:next_run_at, 1.minute.ago) schedule.update_column(:next_run_at, 1.minute.ago)
end end
let(:error_message) { 'some message' } it 'executes the rule schedule service' do
let(:service_result) { ServiceResponse.error(message: error_message) } expect_next_found_instance_of(::Dast::ProfileSchedule) do |schedule|
expect(schedule).to receive(:schedule_next_run!)
end
it 'succeeds and logs the error' do expect(service).to receive(:execute)
expect(logger)
.to receive(:info)
.with(a_hash_including('message' => error_message))
subject subject
end end
end
context 'when schedule does not exist' do context 'when the schedule owner is invalid' do
before do before do
schedule.update_column(:next_run_at, 1.minute.from_now) schedule.update_column(:user_id, nil)
schedule.update_column(:active, true)
end
it 'sets active to false' do
expect { subject }.to change { schedule.reload.active }.to(false)
end
end
end end
it 'executes the rule schedule service' do context 'when service returns an error' do
expect(::AppSec::Dast::Scans::CreateService).not_to receive(:new) before do
schedule.update_column(:next_run_at, 1.minute.ago)
end
subject let(:error_message) { 'some message' }
let(:service_result) { ServiceResponse.error(message: error_message) }
it 'succeeds and logs the error' do
expect(logger)
.to receive(:info)
.with(a_hash_including('message' => error_message))
subject
end
end end
end
context 'when single run schedule exists' do context 'when schedule does not exist' do
before do before do
schedule.update_columns(next_run_at: 1.minute.ago, cadence: {}) schedule.update_column(:next_run_at, 1.minute.from_now)
end
it 'executes the rule schedule service' do
expect(::AppSec::Dast::Scans::CreateService).not_to receive(:new)
subject
end
end end
it 'executes the rule schedule service and deactivate the schedule', :aggregate_failures do context 'when single run schedule exists' do
expect(schedule.repeat?).to be(false) before do
schedule.update_columns(next_run_at: 1.minute.ago, cadence: {})
end
subject it 'executes the rule schedule service and deactivate the schedule', :aggregate_failures do
expect(schedule.repeat?).to be(false)
expect(schedule.reload.active).to be(false) subject
expect(schedule.reload.active).to be(false)
end
end end
end end
end end
......
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