Add support for blocking actions to CI/CD pipeline

This commit is contained in:
Grzegorz Bizon 2017-03-03 12:51:23 +01:00
parent d1b59476df
commit dd24091191
5 changed files with 58 additions and 26 deletions

View file

@ -49,6 +49,10 @@ module Ci
transition any - [:canceled] => :canceled transition any - [:canceled] => :canceled
end end
event :block do
transition any - [:blocked] => :blocked
end
# IMPORTANT # IMPORTANT
# Do not add any operations to this state_machine # Do not add any operations to this state_machine
# Create a separate worker for each new operation # Create a separate worker for each new operation
@ -321,6 +325,7 @@ module Ci
when 'failed' then drop when 'failed' then drop
when 'canceled' then cancel when 'canceled' then cancel
when 'skipped' then skip when 'skipped' then skip
when 'blocked' then block
end end
end end
end end

View file

@ -86,7 +86,7 @@ class CommitStatus < ActiveRecord::Base
commit_status.run_after_commit do commit_status.run_after_commit do
pipeline.try do |pipeline| pipeline.try do |pipeline|
if complete? if complete? || blocked?
PipelineProcessWorker.perform_async(pipeline.id) PipelineProcessWorker.perform_async(pipeline.id)
else else
PipelineUpdateWorker.perform_async(pipeline.id) PipelineUpdateWorker.perform_async(pipeline.id)

View file

@ -2,11 +2,12 @@ module HasStatus
extend ActiveSupport::Concern extend ActiveSupport::Concern
DEFAULT_STATUS = 'created'.freeze DEFAULT_STATUS = 'created'.freeze
BLOCKED_STATUS = 'blocked'.freeze
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped blocked].freeze AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped blocked].freeze
STARTED_STATUSES = %w[running success failed skipped].freeze STARTED_STATUSES = %w[running success failed skipped].freeze
ACTIVE_STATUSES = %w[pending running blocked].freeze ACTIVE_STATUSES = %w[pending running blocked].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].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 class_methods do
def status_sql def status_sql
@ -28,7 +29,7 @@ module HasStatus
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' 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' WHEN (#{blocked})>0 THEN 'blocked'
ELSE 'failed' ELSE 'failed'
END)" END)"

View file

@ -22,6 +22,8 @@ module Ci
def process_stage(index) def process_stage(index)
current_status = status_for_prior_stages(index) current_status = status_for_prior_stages(index)
return if HasStatus::BLOCKED_STATUS == current_status
if HasStatus::COMPLETED_STATUSES.include?(current_status) if HasStatus::COMPLETED_STATUSES.include?(current_status)
created_builds_in_stage(index).select do |build| created_builds_in_stage(index).select do |build|
Gitlab::OptimisticLocking.retry_lock(build) do |subject| Gitlab::OptimisticLocking.retry_lock(build) do |subject|

View file

@ -12,7 +12,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do
project.add_developer(user) project.add_developer(user)
end end
context 'start queuing next builds' do context 'when simple pipeline is defined' do
before do before do
create_build('linux', stage_idx: 0) create_build('linux', stage_idx: 0)
create_build('mac', stage_idx: 0) create_build('mac', stage_idx: 0)
@ -65,7 +65,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end end
end end
context 'properly creates builds when optional actions are defined' do context 'when optional manual actions are defined' do
before do before do
create_build('build', stage_idx: 0) create_build('build', stage_idx: 0)
create_build('test', stage_idx: 1) create_build('test', stage_idx: 1)
@ -77,7 +77,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end end
context 'when builds are successful' do context 'when builds are successful' do
it 'properly creates builds' do it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(builds_names).to eq ['build'] expect(builds_names).to eq ['build']
expect(builds_statuses).to eq ['pending'] expect(builds_statuses).to eq ['pending']
@ -105,7 +105,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end end
context 'when test job fails' do context 'when test job fails' do
it 'properly creates builds' do it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(builds_names).to eq ['build'] expect(builds_names).to eq ['build']
expect(builds_statuses).to eq ['pending'] expect(builds_statuses).to eq ['pending']
@ -133,7 +133,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end end
context 'when test and test_failure jobs fail' do 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(process_pipeline).to be_truthy
expect(builds_names).to eq ['build'] expect(builds_names).to eq ['build']
expect(builds_statuses).to eq ['pending'] expect(builds_statuses).to eq ['pending']
@ -162,7 +162,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end end
context 'when deploy job fails' do context 'when deploy job fails' do
it 'properly creates builds' do it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(builds_names).to eq ['build'] expect(builds_names).to eq ['build']
expect(builds_statuses).to eq ['pending'] expect(builds_statuses).to eq ['pending']
@ -232,7 +232,7 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end end
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 context 'when first stage has only optional manual actions' do
before do before do
create_build('build', stage_idx: 0, when: 'manual', allow_failure: true) 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]) expect(all_builds_statuses).to eq(%w[success skipped pending])
end end
end end
end
context 'when second stage has only on_failure jobs' do context 'when blocking manual actions are defined' do
before do before do
create_build('check', stage_idx: 0) create_build('code:test', stage_idx: 0)
create_build('build', stage_idx: 1, when: 'on_failure') create_build('staging:deploy', stage_idx: 1, when: 'manual')
create_build('test', stage_idx: 2) 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 it 'blocks pipeline on stage with first manual action' do
end process_pipeline
it 'skips second stage and continues on third stage' do expect(builds_names).to eq %w[code:test]
expect(all_builds_statuses).to eq(%w[pending created created]) 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]) expect(builds_names).to eq %w[code:test staging:deploy]
end 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
end end
@ -375,6 +399,10 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end end
end end
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
def all_builds def all_builds
pipeline.builds.order(:stage_idx, :id) pipeline.builds.order(:stage_idx, :id)
end end
@ -395,10 +423,6 @@ describe Ci::ProcessPipelineService, '#execute', :services do
all_builds.pluck(:status) all_builds.pluck(:status)
end end
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
def succeed_pending def succeed_pending
builds.pending.update_all(status: 'success') builds.pending.update_all(status: 'success')
end end