diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 80e11a5b58f..b0ca3e9c189 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -49,6 +49,10 @@ module Ci transition any - [:canceled] => :canceled end + event :block do + transition any - [:blocked] => :blocked + end + # IMPORTANT # Do not add any operations to this state_machine # Create a separate worker for each new operation @@ -321,6 +325,7 @@ module Ci when 'failed' then drop when 'canceled' then cancel when 'skipped' then skip + when 'blocked' then block end end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3ef07ffd0da..9cdabf24677 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -86,7 +86,7 @@ class CommitStatus < ActiveRecord::Base commit_status.run_after_commit do pipeline.try do |pipeline| - if complete? + if complete? || blocked? PipelineProcessWorker.perform_async(pipeline.id) else PipelineUpdateWorker.perform_async(pipeline.id) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 012407a72d8..2b559d8e828 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -2,11 +2,12 @@ module HasStatus extend ActiveSupport::Concern DEFAULT_STATUS = 'created'.freeze + BLOCKED_STATUS = 'blocked'.freeze AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped blocked].freeze STARTED_STATUSES = %w[running success failed skipped].freeze ACTIVE_STATUSES = %w[pending running blocked].freeze COMPLETED_STATUSES = %w[success failed canceled skipped].freeze - ORDERED_STATUSES = %w[failed pending running canceled success skipped].freeze + ORDERED_STATUSES = %w[blocked failed pending running canceled success skipped].freeze class_methods do def status_sql @@ -28,7 +29,7 @@ module HasStatus WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' - WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running' + WHEN (#{running})+(#{pending})>0 THEN 'running' WHEN (#{blocked})>0 THEN 'blocked' ELSE 'failed' END)" diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 24428d9afae..30fccbddc33 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -22,6 +22,8 @@ module Ci def process_stage(index) current_status = status_for_prior_stages(index) + return if HasStatus::BLOCKED_STATUS == current_status + if HasStatus::COMPLETED_STATUSES.include?(current_status) created_builds_in_stage(index).select do |build| Gitlab::OptimisticLocking.retry_lock(build) do |subject| diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 2cceca40a46..31835d6836c 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -12,7 +12,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do project.add_developer(user) end - context 'start queuing next builds' do + context 'when simple pipeline is defined' do before do create_build('linux', stage_idx: 0) create_build('mac', stage_idx: 0) @@ -65,7 +65,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do end end - context 'properly creates builds when optional actions are defined' do + context 'when optional manual actions are defined' do before do create_build('build', stage_idx: 0) create_build('test', stage_idx: 1) @@ -77,7 +77,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do end context 'when builds are successful' do - it 'properly creates builds' do + it 'properly processes the pipeline' do expect(process_pipeline).to be_truthy expect(builds_names).to eq ['build'] expect(builds_statuses).to eq ['pending'] @@ -105,7 +105,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do end context 'when test job fails' do - it 'properly creates builds' do + it 'properly processes the pipeline' do expect(process_pipeline).to be_truthy expect(builds_names).to eq ['build'] expect(builds_statuses).to eq ['pending'] @@ -133,7 +133,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do end context 'when test and test_failure jobs fail' do - it 'properly creates builds' do + it 'properly processes the pipeline' do expect(process_pipeline).to be_truthy expect(builds_names).to eq ['build'] expect(builds_statuses).to eq ['pending'] @@ -162,7 +162,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do end context 'when deploy job fails' do - it 'properly creates builds' do + it 'properly processes the pipeline' do expect(process_pipeline).to be_truthy expect(builds_names).to eq ['build'] expect(builds_statuses).to eq ['pending'] @@ -232,7 +232,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do end end - context 'when there are manual/on_failure jobs in earlier stages' do + context 'when there are manual action in earlier stages' do context 'when first stage has only optional manual actions' do before do create_build('build', stage_idx: 0, when: 'manual', allow_failure: true) @@ -264,23 +264,47 @@ describe Ci::ProcessPipelineService, '#execute', :services do expect(all_builds_statuses).to eq(%w[success skipped pending]) end end + end - context 'when second stage has only on_failure jobs' do - before do - create_build('check', stage_idx: 0) - create_build('build', stage_idx: 1, when: 'on_failure') - create_build('test', stage_idx: 2) + context 'when blocking manual actions are defined' do + before do + create_build('code:test', stage_idx: 0) + create_build('staging:deploy', stage_idx: 1, when: 'manual') + create_build('staging:test', stage_idx: 2, when: 'on_success') + create_build('production:deploy', stage_idx: 3, when: 'manual') + create_build('production:test', stage_idx: 4, when: 'always') + end - process_pipeline - end + it 'blocks pipeline on stage with first manual action' do + process_pipeline - it 'skips second stage and continues on third stage' do - expect(all_builds_statuses).to eq(%w[pending created created]) + expect(builds_names).to eq %w[code:test] + expect(builds_statuses).to eq %w[pending] + expect(pipeline.reload.status).to eq 'pending' - builds.first.success + succeed_running_or_pending - expect(all_builds_statuses).to eq(%w[success skipped pending]) - end + expect(builds_names).to eq %w[code:test staging:deploy] + expect(builds_statuses).to eq %w[success blocked] + expect(pipeline.reload.status).to eq 'blocked' + end + end + + context 'when second stage has only on_failure jobs' do + before do + create_build('check', stage_idx: 0) + create_build('build', stage_idx: 1, when: 'on_failure') + create_build('test', stage_idx: 2) + + process_pipeline + end + + it 'skips second stage and continues on third stage' do + expect(all_builds_statuses).to eq(%w[pending created created]) + + builds.first.success + + expect(all_builds_statuses).to eq(%w[success skipped pending]) end end @@ -375,6 +399,10 @@ describe Ci::ProcessPipelineService, '#execute', :services do end end + def process_pipeline + described_class.new(pipeline.project, user).execute(pipeline) + end + def all_builds pipeline.builds.order(:stage_idx, :id) end @@ -395,10 +423,6 @@ describe Ci::ProcessPipelineService, '#execute', :services do all_builds.pluck(:status) end - def process_pipeline - described_class.new(pipeline.project, user).execute(pipeline) - end - def succeed_pending builds.pending.update_all(status: 'success') end