Add support for blocking actions to CI/CD pipeline
This commit is contained in:
parent
d1b59476df
commit
dd24091191
5 changed files with 58 additions and 26 deletions
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)"
|
||||||
|
|
|
@ -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|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue