Commit 79b8f02b authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'pipeline-blocking-actions' into 'master'

Make manual actions blocking

Closes #26360 and #22628

See merge request !9585
parents b696cbc5 3b35d1f8
...@@ -63,6 +63,10 @@ module Ci ...@@ -63,6 +63,10 @@ module Ci
end end
state_machine :status do state_machine :status do
event :actionize do
transition created: :manual
end
after_transition any => [:pending] do |build| after_transition any => [:pending] do |build|
build.run_after_commit do build.run_after_commit do
BuildQueueWorker.perform_async(id) BuildQueueWorker.perform_async(id)
...@@ -94,16 +98,21 @@ module Ci ...@@ -94,16 +98,21 @@ module Ci
.fabricate! .fabricate!
end end
def manual?
self.when == 'manual'
end
def other_actions def other_actions
pipeline.manual_actions.where.not(name: name) pipeline.manual_actions.where.not(name: name)
end end
def playable? def playable?
project.builds_enabled? && commands.present? && manual? && skipped? project.builds_enabled? && has_commands? &&
action? && manual?
end
def action?
self.when == 'manual'
end
def has_commands?
commands.present?
end end
def play(current_user) def play(current_user)
...@@ -122,7 +131,7 @@ module Ci ...@@ -122,7 +131,7 @@ module Ci
end end
def retryable? def retryable?
project.builds_enabled? && commands.present? && project.builds_enabled? && has_commands? &&
(success? || failed? || canceled?) (success? || failed? || canceled?)
end end
...@@ -552,7 +561,7 @@ module Ci ...@@ -552,7 +561,7 @@ module Ci
] ]
variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag? variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if manual? variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if action?
variables variables
end end
......
...@@ -49,6 +49,10 @@ module Ci ...@@ -49,6 +49,10 @@ module Ci
transition any - [:canceled] => :canceled transition any - [:canceled] => :canceled
end end
event :block do
transition any - [:manual] => :manual
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 ...@@ -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 'manual' then block
end end
end end
end end
......
...@@ -29,9 +29,11 @@ class CommitStatus < ActiveRecord::Base ...@@ -29,9 +29,11 @@ class CommitStatus < ActiveRecord::Base
end end
scope :exclude_ignored, -> do scope :exclude_ignored, -> do
# We want to ignore failed_but_allowed jobs # We want to ignore failed but allowed to fail jobs.
#
# TODO, we also skip ignored optional manual actions.
where("allow_failure = ? OR status IN (?)", where("allow_failure = ? OR status IN (?)",
false, all_state_names - [:failed, :canceled]) false, all_state_names - [:failed, :canceled, :manual])
end end
scope :retried, -> { where.not(id: latest) } scope :retried, -> { where.not(id: latest) }
...@@ -42,11 +44,11 @@ class CommitStatus < ActiveRecord::Base ...@@ -42,11 +44,11 @@ class CommitStatus < ActiveRecord::Base
state_machine :status do state_machine :status do
event :enqueue do event :enqueue do
transition [:created, :skipped] => :pending transition [:created, :skipped, :manual] => :pending
end end
event :process do event :process do
transition skipped: :created transition [:skipped, :manual] => :created
end end
event :run do event :run do
...@@ -66,7 +68,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -66,7 +68,7 @@ class CommitStatus < ActiveRecord::Base
end end
event :cancel do event :cancel do
transition [:created, :pending, :running] => :canceled transition [:created, :pending, :running, :manual] => :canceled
end end
before_transition created: [:pending, :running] do |commit_status| before_transition created: [:pending, :running] do |commit_status|
...@@ -86,7 +88,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -86,7 +88,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? || manual?
PipelineProcessWorker.perform_async(pipeline.id) PipelineProcessWorker.perform_async(pipeline.id)
else else
PipelineUpdateWorker.perform_async(pipeline.id) PipelineUpdateWorker.perform_async(pipeline.id)
......
...@@ -2,22 +2,21 @@ module HasStatus ...@@ -2,22 +2,21 @@ module HasStatus
extend ActiveSupport::Concern extend ActiveSupport::Concern
DEFAULT_STATUS = 'created'.freeze DEFAULT_STATUS = 'created'.freeze
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped].freeze BLOCKED_STATUS = 'manual'.freeze
STARTED_STATUSES = %w[running success failed skipped].freeze AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped manual].freeze
STARTED_STATUSES = %w[running success failed skipped manual].freeze
ACTIVE_STATUSES = %w[pending running].freeze ACTIVE_STATUSES = %w[pending running].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[manual failed pending running canceled success skipped].freeze
class_methods do class_methods do
def status_sql def status_sql
scope = if respond_to?(:exclude_ignored) scope = respond_to?(:exclude_ignored) ? exclude_ignored : all
exclude_ignored
else
all
end
builds = scope.select('count(*)').to_sql builds = scope.select('count(*)').to_sql
created = scope.created.select('count(*)').to_sql created = scope.created.select('count(*)').to_sql
success = scope.success.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql
manual = scope.manual.select('count(*)').to_sql
pending = scope.pending.select('count(*)').to_sql pending = scope.pending.select('count(*)').to_sql
running = scope.running.select('count(*)').to_sql running = scope.running.select('count(*)').to_sql
skipped = scope.skipped.select('count(*)').to_sql skipped = scope.skipped.select('count(*)').to_sql
...@@ -30,7 +29,8 @@ module HasStatus ...@@ -30,7 +29,8 @@ 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 (#{manual})>0 THEN 'manual'
ELSE 'failed' ELSE 'failed'
END)" END)"
end end
...@@ -63,6 +63,7 @@ module HasStatus ...@@ -63,6 +63,7 @@ module HasStatus
state :success, value: 'success' state :success, value: 'success'
state :canceled, value: 'canceled' state :canceled, value: 'canceled'
state :skipped, value: 'skipped' state :skipped, value: 'skipped'
state :manual, value: 'manual'
end end
scope :created, -> { where(status: 'created') } scope :created, -> { where(status: 'created') }
...@@ -73,12 +74,13 @@ module HasStatus ...@@ -73,12 +74,13 @@ module HasStatus
scope :failed, -> { where(status: 'failed') } scope :failed, -> { where(status: 'failed') }
scope :canceled, -> { where(status: 'canceled') } scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') } scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
scope :cancelable, -> do scope :cancelable, -> do
where(status: [:running, :pending, :created]) where(status: [:running, :pending, :created, :manual])
end end
end end
...@@ -94,6 +96,10 @@ module HasStatus ...@@ -94,6 +96,10 @@ module HasStatus
COMPLETED_STATUSES.include?(status) COMPLETED_STATUSES.include?(status)
end end
def blocked?
BLOCKED_STATUS == status
end
private private
def calculate_duration def calculate_duration
......
...@@ -12,7 +12,7 @@ class BuildEntity < Grape::Entity ...@@ -12,7 +12,7 @@ class BuildEntity < Grape::Entity
path_to(:retry_namespace_project_build, build) path_to(:retry_namespace_project_build, build)
end end
expose :play_path, if: ->(build, _) { build.manual? } do |build| expose :play_path, if: ->(build, _) { build.playable? } do |build|
path_to(:play_namespace_project_build, build) path_to(:play_namespace_project_build, build)
end end
......
...@@ -22,6 +22,8 @@ module Ci ...@@ -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|
...@@ -33,7 +35,7 @@ module Ci ...@@ -33,7 +35,7 @@ module Ci
def process_build(build, current_status) def process_build(build, current_status)
if valid_statuses_for_when(build.when).include?(current_status) if valid_statuses_for_when(build.when).include?(current_status)
build.enqueue build.action? ? build.actionize : build.enqueue
true true
else else
build.skip build.skip
...@@ -49,6 +51,8 @@ module Ci ...@@ -49,6 +51,8 @@ module Ci
%w[failed] %w[failed]
when 'always' when 'always'
%w[success failed skipped] %w[success failed skipped]
when 'manual'
%w[success]
else else
[] []
end end
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
%span.label.label-info triggered %span.label.label-info triggered
- if build.try(:allow_failure) - if build.try(:allow_failure)
%span.label.label-danger allowed to fail %span.label.label-danger allowed to fail
- if build.manual? - if build.action?
%span.label.label-info manual %span.label.label-info manual
- if pipeline_link - if pipeline_link
......
---
title: Make it possible to configure blocking manual actions
merge_request: 9585
author:
class MigrateLegacyManualActions < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
execute <<-EOS
UPDATE ci_builds SET status = 'manual', allow_failure = true
WHERE ci_builds.when = 'manual' AND ci_builds.status = 'skipped';
EOS
end
def down
execute <<-EOS
UPDATE ci_builds SET status = 'skipped', allow_failure = false
WHERE ci_builds.when = 'manual' AND ci_builds.status = 'manual';
EOS
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170305203726) do ActiveRecord::Schema.define(version: 20170306170512) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -545,13 +545,30 @@ The above script will: ...@@ -545,13 +545,30 @@ The above script will:
Manual actions are a special type of job that are not executed automatically; Manual actions are a special type of job that are not executed automatically;
they need to be explicitly started by a user. Manual actions can be started they need to be explicitly started by a user. Manual actions can be started
from pipeline, build, environment, and deployment views. You can execute the from pipeline, build, environment, and deployment views.
same manual action multiple times.
An example usage of manual actions is deployment to production. An example usage of manual actions is deployment to production.
Read more at the [environments documentation][env-manual]. Read more at the [environments documentation][env-manual].
Manual actions can be either optional or blocking. Blocking manual action will
block execution of the pipeline at stage this action is defined in. It is
possible to resume execution of the pipeline when someone executes a blocking
manual actions by clicking a _play_ button.
When pipeline is blocked it will not be merged if Merge When Pipeline Succeeds
is set. Blocked pipelines also do have a special status, called _manual_.
Manual actions are non-blocking by default. If you want to make manual action
blocking, it is necessary to add `allow_failure: false` to the job's definition
in `.gitlab-ci.yml`.
Optional manual actions have `allow_failure: true` set by default.
**Statuses of optional actions do not contribute to overall pipeline status.**
> Blocking manual actions were introduced in GitLab 9.0
### environment ### environment
> >
......
...@@ -58,7 +58,7 @@ module Ci ...@@ -58,7 +58,7 @@ module Ci
commands: job[:commands], commands: job[:commands],
tag_list: job[:tags] || [], tag_list: job[:tags] || [],
name: job[:name].to_s, name: job[:name].to_s,
allow_failure: job[:allow_failure] || false, allow_failure: job[:ignore],
when: job[:when] || 'on_success', when: job[:when] || 'on_success',
environment: job[:environment_name], environment: job[:environment_name],
coverage_regex: job[:coverage], coverage_regex: job[:coverage],
......
...@@ -104,6 +104,14 @@ module Gitlab ...@@ -104,6 +104,14 @@ module Gitlab
(before_script_value.to_a + script_value.to_a).join("\n") (before_script_value.to_a + script_value.to_a).join("\n")
end end
def manual_action?
self.when == 'manual'
end
def ignored?
allow_failure.nil? ? manual_action? : allow_failure
end
private private
def inherit!(deps) def inherit!(deps)
...@@ -135,7 +143,8 @@ module Gitlab ...@@ -135,7 +143,8 @@ module Gitlab
environment_name: environment_defined? ? environment_value[:name] : nil, environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil, coverage: coverage_defined? ? coverage_value : nil,
artifacts: artifacts_value, artifacts: artifacts_value,
after_script: after_script_value } after_script: after_script_value,
ignore: ignored? }
end end
end end
end end
......
...@@ -5,22 +5,10 @@ module Gitlab ...@@ -5,22 +5,10 @@ module Gitlab
class Play < SimpleDelegator class Play < SimpleDelegator
include Status::Extended include Status::Extended
def text
'manual'
end
def label def label
'manual play action' 'manual play action'
end end
def icon
'icon_status_manual'
end
def group
'manual'
end
def has_action? def has_action?
can?(user, :update_build, subject) can?(user, :update_build, subject)
end end
......
...@@ -5,22 +5,10 @@ module Gitlab ...@@ -5,22 +5,10 @@ module Gitlab
class Stop < SimpleDelegator class Stop < SimpleDelegator
include Status::Extended include Status::Extended
def text
'manual'
end
def label def label
'manual stop action' 'manual stop action'
end end
def icon
'icon_status_manual'
end
def group
'manual'
end
def has_action? def has_action?
can?(user, :update_build, subject) can?(user, :update_build, subject)
end end
......
module Gitlab
module Ci
module Status
class Manual < Status::Core
def text
'manual'
end
def label
'manual action'
end
def icon
'icon_status_manual'
end
end
end
end
end
...@@ -39,7 +39,7 @@ module Gitlab ...@@ -39,7 +39,7 @@ module Gitlab
started_at: build.started_at, started_at: build.started_at,
finished_at: build.finished_at, finished_at: build.finished_at,
when: build.when, when: build.when,
manual: build.manual?, manual: build.action?,
user: build.user.try(:hook_attrs), user: build.user.try(:hook_attrs),
runner: build.runner && runner_hook_attrs(build.runner), runner: build.runner && runner_hook_attrs(build.runner),
artifacts_file: { artifacts_file: {
......
...@@ -57,7 +57,7 @@ FactoryGirl.define do ...@@ -57,7 +57,7 @@ FactoryGirl.define do
end end
trait :manual do trait :manual do
status 'skipped' status 'manual'
self.when 'manual' self.when 'manual'
end end
...@@ -71,8 +71,11 @@ FactoryGirl.define do ...@@ -71,8 +71,11 @@ FactoryGirl.define do
allow_failure true allow_failure true
end end
trait :ignored do
allowed_to_fail
end
trait :playable do trait :playable do
skipped
manual manual
end end
......
...@@ -35,6 +35,10 @@ FactoryGirl.define do ...@@ -35,6 +35,10 @@ FactoryGirl.define do
status 'created' status 'created'
end end
trait :manual do
status 'manual'
end
after(:build) do |build, evaluator| after(:build) do |build, evaluator|
build.project = build.pipeline.project build.project = build.pipeline.project
end end
......
...@@ -13,7 +13,7 @@ feature 'Environment', :feature do ...@@ -13,7 +13,7 @@ feature 'Environment', :feature do
feature 'environment details page' do feature 'environment details page' do
given!(:environment) { create(:environment, project: project) } given!(:environment) { create(:environment, project: project) }
given!(:deployment) { } given!(:deployment) { }
given!(:manual) { } given!(:action) { }
before do before do
visit_environment(environment) visit_environment(environment)
...@@ -69,17 +69,23 @@ feature 'Environment', :feature do ...@@ -69,17 +69,23 @@ feature 'Environment', :feature do
end end
context 'with manual action' do context 'with manual action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } given(:action) do
create(:ci_build, :manual, pipeline: pipeline,
name: 'deploy to production')
end
scenario 'does show a play button' do scenario 'does show a play button' do
expect(page).to have_link(manual.name.humanize) expect(page).to have_link(action.name.humanize)
end end
scenario 'does allow to play manual action' do scenario 'does allow to play manual action' do
expect(manual).to be_skipped expect(action).to be_manual
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
expect(page).to have_content(manual.name) expect { click_link(action.name.humanize) }
expect(manual.reload).to be_pending .not_to change { Ci::Pipeline.count }
expect(page).to have_content(action.name)
expect(action.reload).to be_pending
end end
context 'with external_url' do context 'with external_url' do
...@@ -130,8 +136,16 @@ feature 'Environment', :feature do ...@@ -130,8 +136,16 @@ feature 'Environment', :feature do
context 'when environment is available' do context 'when environment is available' do
context 'with stop action' do context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } given(:action) do
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } create(:ci_build, :manual, pipeline: pipeline,
name: 'close_app')
end
given(:deployment) do
create(:deployment, environment: environment,
deployable: build,
on_stop: 'close_app')
end
scenario 'does show stop button' do scenario 'does show stop button' do
expect(page).to have_link('Stop') expect(page).to have_link('Stop')
......
...@@ -12,7 +12,7 @@ feature 'Environments page', :feature, :js do ...@@ -12,7 +12,7 @@ feature 'Environments page', :feature, :js do
given!(:environment) { } given!(:environment) { }
given!(:deployment) { } given!(:deployment) { }
given!(:manual) { } given!(:action) { }
before do before do
visit_environments(project) visit_environments(project)
...@@ -90,7 +90,7 @@ feature 'Environments page', :feature, :js do ...@@ -90,7 +90,7 @@ feature 'Environments page', :feature, :js do
given(:pipeline) { create(:ci_pipeline, project: project) } given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) } given(:build) { create(:ci_build, pipeline: pipeline) }
given(:manual) do given(:action) do
create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production')
end end
...@@ -102,19 +102,19 @@ feature 'Environments page', :feature, :js do ...@@ -102,19 +102,19 @@ feature 'Environments page', :feature, :js do
scenario 'does show a play button' do scenario 'does show a play button' do
find('.js-dropdown-play-icon-container').click find('.js-dropdown-play-icon-container').click
expect(page).to have_content(manual.name.humanize) expect(page).to have_content(action.name.humanize)
end end
scenario 'does allow to play manual action', js: true do scenario 'does allow to play manual action', js: true do
expect(manual).to be_skipped expect(action).to be_manual
find('.js-dropdown-play-icon-container').click find('.js-dropdown-play-icon-container').click
expect(page).to have_content(manual.name.humanize) expect(page).to have_content(action.name.humanize)
expect { click_link(manual.name.humanize) } expect { click_link(action.name.humanize) }
.not_to change { Ci::Pipeline.count } .not_to change { Ci::Pipeline.count }
expect(manual.reload).to be_pending expect(action.reload).to be_pending
end end
scenario 'does show build name and id' do scenario 'does show build name and id' do
...@@ -144,8 +144,15 @@ feature 'Environments page', :feature, :js do ...@@ -144,8 +144,15 @@ feature 'Environments page', :feature, :js do
end end
context 'with stop action' do context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } given(:action) do
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } create(:ci_build, :manual, pipeline: pipeline, name: 'close_app')
end
given(:deployment) do
create(:deployment, environment: environment,
deployable: build,
on_stop: 'close_app')
end
scenario 'does show stop button' do scenario 'does show stop button' do
expect(page).to have_selector('.stop-env-link') expect(page).to have_selector('.stop-env-link')
......
...@@ -15,9 +15,9 @@ module Ci ...@@ -15,9 +15,9 @@ module Ci
end end
describe '#build_attributes' do describe '#build_attributes' do
describe 'coverage entry' do
subject { described_class.new(config, path).build_attributes(:rspec) } subject { described_class.new(config, path).build_attributes(:rspec) }
describe 'coverage entry' do
describe 'code coverage regexp' do describe 'code coverage regexp' do
let(:config) do let(:config) do
YAML.dump(rspec: { script: 'rspec', YAML.dump(rspec: { script: 'rspec',
...@@ -30,6 +30,56 @@ module Ci ...@@ -30,6 +30,56 @@ module Ci
end end
end end
end end
describe 'allow failure entry' do
context 'when job is a manual action' do
context 'when allow_failure is defined' do
let(:config) do
YAML.dump(rspec: { script: 'rspec',
when: 'manual',
allow_failure: false })
end
it 'is not allowed to fail' do
expect(subject[:allow_failure]).to be false
end
end
context 'when allow_failure is not defined' do
let(:config) do
YAML.dump(rspec: { script: 'rspec',
when: 'manual' })
end
it 'is allowed to fail' do
expect(subject[:allow_failure]).to be true
end
end
end
context 'when job is not a manual action' do
context 'when allow_failure is defined' do
let(:config) do
YAML.dump(rspec: { script: 'rspec',
allow_failure: false })
end
it 'is not allowed to fail' do
expect(subject[:allow_failure]).to be false
end
end
context 'when allow_failure is not defined' do
let(:config) do
YAML.dump(rspec: { script: 'rspec' })
end
it 'is not allowed to fail' do
expect(subject[:allow_failure]).to be false
end
end
end
end
end end
describe "#builds_for_ref" do describe "#builds_for_ref" do
......
...@@ -155,6 +155,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -155,6 +155,7 @@ describe Gitlab::Ci::Config::Entry::Global do
stage: 'test', stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] }, cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: { VAR: 'value' }, variables: { VAR: 'value' },
ignore: false,
after_script: ['make clean'] }, after_script: ['make clean'] },
spinach: { name: :spinach, spinach: { name: :spinach,
before_script: [], before_script: [],
...@@ -165,6 +166,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -165,6 +166,7 @@ describe Gitlab::Ci::Config::Entry::Global do
stage: 'test', stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] }, cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: {}, variables: {},
ignore: false,
after_script: ['make clean'] }, after_script: ['make clean'] },
) )
end end
......
...@@ -144,6 +144,7 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -144,6 +144,7 @@ describe Gitlab::Ci::Config::Entry::Job do
script: %w[rspec], script: %w[rspec],
commands: "ls\npwd\nrspec", commands: "ls\npwd\nrspec",
stage: 'test', stage: 'test',
ignore: false,
after_script: %w[cleanup]) after_script: %w[cleanup])
end end
end end
...@@ -159,4 +160,82 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -159,4 +160,82 @@ describe Gitlab::Ci::Config::Entry::Job do
end end
end end
end end
describe '#manual_action?' do
context 'when job is a manual action' do
let(:config) { { script: 'deploy', when: 'manual' } }
it 'is a manual action' do
expect(entry).to be_manual_action
end
end
context 'when job is not a manual action' do
let(:config) { { script: 'deploy' } }
it 'is not a manual action' do
expect(entry).not_to be_manual_action
end
end
end
describe '#ignored?' do
context 'when job is a manual action' do
context 'when it is not specified if job is allowed to fail' do
let(:config) do
{ script: 'deploy', when: 'manual' }
end
it 'is an ignored job' do
expect(entry).to be_ignored
end
end
context 'when job is allowed to fail' do
let(:config) do
{ script: 'deploy', when: 'manual', allow_failure: true }
end
it 'is an ignored job' do
expect(entry).to be_ignored
end
end
context 'when job is not allowed to fail' do
let(:config) do
{ script: 'deploy', when: 'manual', allow_failure: false }
end
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
end
end
context 'when job is not a manual action' do
context 'when it is not specified if job is allowed to fail' do
let(:config) { { script: 'deploy' } }
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
end
context 'when job is allowed to fail' do
let(:config) { { script: 'deploy', allow_failure: true } }
it 'is an ignored job' do
expect(entry).to be_ignored
end
end
context 'when job is not allowed to fail' do
let(:config) { { script: 'deploy', allow_failure: false } }
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
end
end
end
end end
...@@ -62,10 +62,12 @@ describe Gitlab::Ci::Config::Entry::Jobs do ...@@ -62,10 +62,12 @@ describe Gitlab::Ci::Config::Entry::Jobs do
rspec: { name: :rspec, rspec: { name: :rspec,
script: %w[rspec], script: %w[rspec],
commands: 'rspec', commands: 'rspec',
ignore: false,
stage: 'test' }, stage: 'test' },
spinach: { name: :spinach, spinach: { name: :spinach,
script: %w[spinach], script: %w[spinach],
commands: 'spinach', commands: 'spinach',
ignore: false,
stage: 'test' }) stage: 'test' })
end end
end end
......
...@@ -192,7 +192,7 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -192,7 +192,7 @@ describe Gitlab::Ci::Status::Build::Factory do
let(:build) { create(:ci_build, :playable) } let(:build) { create(:ci_build, :playable) }
it 'matches correct core status' do it 'matches correct core status' do
expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped expect(factory.core_status).to be_a Gitlab::Ci::Status::Manual
end end
it 'matches correct extended statuses' do it 'matches correct extended statuses' do
...@@ -200,12 +200,13 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -200,12 +200,13 @@ describe Gitlab::Ci::Status::Build::Factory do
.to eq [Gitlab::Ci::Status::Build::Play] .to eq [Gitlab::Ci::Status::Build::Play]
end end
it 'fabricates a core skipped status' do it 'fabricates a play detailed status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Play expect(status).to be_a Gitlab::Ci::Status::Build::Play
end end
it 'fabricates status with correct details' do it 'fabricates status with correct details' do
expect(status.text).to eq 'manual' expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual' expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual play action' expect(status.label).to eq 'manual play action'
expect(status).to have_details expect(status).to have_details
...@@ -218,7 +219,7 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -218,7 +219,7 @@ describe Gitlab::Ci::Status::Build::Factory do
let(:build) { create(:ci_build, :playable, :teardown_environment) } let(:build) { create(:ci_build, :playable, :teardown_environment) }
it 'matches correct core status' do it 'matches correct core status' do
expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped expect(factory.core_status).to be_a Gitlab::Ci::Status::Manual
end end
it 'matches correct extended statuses' do it 'matches correct extended statuses' do
...@@ -226,12 +227,13 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -226,12 +227,13 @@ describe Gitlab::Ci::Status::Build::Factory do
.to eq [Gitlab::Ci::Status::Build::Stop] .to eq [Gitlab::Ci::Status::Build::Stop]
end end
it 'fabricates a core skipped status' do it 'fabricates a stop detailed status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Stop expect(status).to be_a Gitlab::Ci::Status::Build::Stop
end end
it 'fabricates status with correct details' do it 'fabricates status with correct details' do
expect(status.text).to eq 'manual' expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual' expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual stop action' expect(status.label).to eq 'manual stop action'
expect(status).to have_details expect(status).to have_details
......
...@@ -6,22 +6,10 @@ describe Gitlab::Ci::Status::Build::Play do ...@@ -6,22 +6,10 @@ describe Gitlab::Ci::Status::Build::Play do
subject { described_class.new(status) } subject { described_class.new(status) }
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do describe '#label' do
it { expect(subject.label).to eq 'manual play action' } it { expect(subject.label).to eq 'manual play action' }
end end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_manual' }
end
describe '#group' do
it { expect(subject.group).to eq 'manual' }
end
describe 'action details' do describe 'action details' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:build) { create(:ci_build) } let(:build) { create(:ci_build) }
......
...@@ -8,22 +8,10 @@ describe Gitlab::Ci::Status::Build::Stop do ...@@ -8,22 +8,10 @@ describe Gitlab::Ci::Status::Build::Stop do
described_class.new(status) described_class.new(status)
end end
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do describe '#label' do
it { expect(subject.label).to eq 'manual stop action' } it { expect(subject.label).to eq 'manual stop action' }
end end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_manual' }
end
describe '#group' do
it { expect(subject.group).to eq 'manual' }
end
describe 'action details' do describe 'action details' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:build) { create(:ci_build) } let(:build) { create(:ci_build) }
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Canceled do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Canceled do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'canceled' } it { expect(subject.text).to eq 'canceled' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Created do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Created do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'created' } it { expect(subject.text).to eq 'created' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Failed do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Failed do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'failed' } it { expect(subject.text).to eq 'failed' }
end end
describe '#label' do describe '#label' do
......
require 'spec_helper'
describe Gitlab::Ci::Status::Manual do
subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do
it { expect(subject.label).to eq 'manual action' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_manual' }
end
describe '#group' do
it { expect(subject.group).to eq 'manual' }
end
end
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Pending do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Pending do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'pending' } it { expect(subject.text).to eq 'pending' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Running do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Running do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'running' } it { expect(subject.text).to eq 'running' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Skipped do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Skipped do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'skipped' } it { expect(subject.text).to eq 'skipped' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Success do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Success do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'passed' } it { expect(subject.text).to eq 'passed' }
end end
describe '#label' do describe '#label' do
......
...@@ -20,6 +20,30 @@ describe Ci::Build, :models do ...@@ -20,6 +20,30 @@ describe Ci::Build, :models do
it { is_expected.to validate_presence_of :ref } it { is_expected.to validate_presence_of :ref }
it { is_expected.to respond_to :trace_html } it { is_expected.to respond_to :trace_html }
describe '#actionize' do
context 'when build is a created' do
before do
build.update_column(:status, :created)
end
it 'makes build a manual action' do
expect(build.actionize).to be true
expect(build.reload).to be_manual
end
end
context 'when build is not created' do
before do
build.update_column(:status, :pending)
end
it 'does not change build status' do
expect(build.actionize).to be false
expect(build.reload).to be_pending
end
end
end
describe '#any_runners_online?' do describe '#any_runners_online?' do
subject { build.any_runners_online? } subject { build.any_runners_online? }
...@@ -587,13 +611,21 @@ describe Ci::Build, :models do ...@@ -587,13 +611,21 @@ describe Ci::Build, :models do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'and build.status is failed' do context 'and build status is failed' do
before do before do
build.status = 'failed' build.status = 'failed'
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
context 'when build is a manual action' do
before do
build.status = 'manual'
end
it { is_expected.to be_falsey }
end
end end
end end
...@@ -682,12 +714,12 @@ describe Ci::Build, :models do ...@@ -682,12 +714,12 @@ describe Ci::Build, :models do
end end
end end
describe '#manual?' do describe '#action?' do
before do before do
build.update(when: value) build.update(when: value)
end end
subject { build.manual? } subject { build.action? }
context 'when is set to manual' do context 'when is set to manual' do
let(:value) { 'manual' } let(:value) { 'manual' }
...@@ -703,14 +735,50 @@ describe Ci::Build, :models do ...@@ -703,14 +735,50 @@ describe Ci::Build, :models do
end end
end end
describe '#has_commands?' do
context 'when build has commands' do
let(:build) do
create(:ci_build, commands: 'rspec')
end
it 'has commands' do
expect(build).to have_commands
end
end
context 'when does not have commands' do
context 'when commands are an empty string' do
let(:build) do
create(:ci_build, commands: '')
end
it 'has no commands' do
expect(build).not_to have_commands
end
end
context 'when commands are not set at all' do
let(:build) do
create(:ci_build, commands: nil)
end
it 'has no commands' do
expect(build).not_to have_commands
end
end
end
end
describe '#has_tags?' do describe '#has_tags?' do
context 'when build has tags' do context 'when build has tags' do
subject { create(:ci_build, tag_list: ['tag']) } subject { create(:ci_build, tag_list: ['tag']) }
it { is_expected.to have_tags } it { is_expected.to have_tags }
end end
context 'when build does not have tags' do context 'when build does not have tags' do
subject { create(:ci_build, tag_list: []) } subject { create(:ci_build, tag_list: []) }
it { is_expected.not_to have_tags } it { is_expected.not_to have_tags }
end end
end end
......
...@@ -24,6 +24,14 @@ describe Ci::Pipeline, models: true do ...@@ -24,6 +24,14 @@ describe Ci::Pipeline, models: true do
it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha } it { is_expected.to respond_to :short_sha }
describe '#block' do
it 'changes pipeline status to manual' do
expect(pipeline.block).to be true
expect(pipeline.reload).to be_manual
expect(pipeline.reload).to be_blocked
end
end
describe '#valid_commit_sha' do describe '#valid_commit_sha' do
context 'commit.sha can not start with 00000000' do context 'commit.sha can not start with 00000000' do
before do before do
...@@ -635,6 +643,14 @@ describe Ci::Pipeline, models: true do ...@@ -635,6 +643,14 @@ describe Ci::Pipeline, models: true do
end end
end end
context 'when pipeline is blocked' do
let(:pipeline) { create(:ci_pipeline, status: :manual) }
it 'returns detailed status for blocked pipeline' do
expect(subject.text).to eq 'manual'
end
end
context 'when pipeline is successful but with warnings' do context 'when pipeline is successful but with warnings' do
let(:pipeline) { create(:ci_pipeline, status: :success) } let(:pipeline) { create(:ci_pipeline, status: :success) }
......
...@@ -158,7 +158,7 @@ describe CommitStatus, :models do ...@@ -158,7 +158,7 @@ describe CommitStatus, :models do
end end
end end
describe '.exclude_ignored' do describe '.after_stage' do
subject { described_class.after_stage(0) } subject { described_class.after_stage(0) }
let(:statuses) do let(:statuses) do
...@@ -185,11 +185,32 @@ describe CommitStatus, :models do ...@@ -185,11 +185,32 @@ describe CommitStatus, :models do
create_status(allow_failure: true, status: 'success'), create_status(allow_failure: true, status: 'success'),
create_status(allow_failure: true, status: 'failed'), create_status(allow_failure: true, status: 'failed'),
create_status(allow_failure: false, status: 'success'), create_status(allow_failure: false, status: 'success'),
create_status(allow_failure: false, status: 'failed')] create_status(allow_failure: false, status: 'failed'),
create_status(allow_failure: true, status: 'manual'),
create_status(allow_failure: false, status: 'manual')]
end
it 'returns statuses without what we want to ignore' do
is_expected.to eq(statuses.values_at(0, 1, 2, 3, 4, 5, 6, 8, 9, 11))
end
end
describe '.failed_but_allowed' do
subject { described_class.failed_but_allowed.order(:id) }
let(:statuses) do
[create_status(allow_failure: true, status: 'success'),
create_status(allow_failure: true, status: 'failed'),
create_status(allow_failure: false, status: 'success'),
create_status(allow_failure: false, status: 'failed'),
create_status(allow_failure: true, status: 'canceled'),
create_status(allow_failure: false, status: 'canceled'),
create_status(allow_failure: true, status: 'manual'),
create_status(allow_failure: false, status: 'manual')]
end end
it 'returns statuses without what we want to ignore' do it 'returns statuses without what we want to ignore' do
is_expected.to eq(statuses.values_at(0, 1, 2, 3, 4, 5, 6, 8, 9)) is_expected.to eq(statuses.values_at(1, 4))
end end
end end
......
...@@ -109,6 +109,24 @@ describe HasStatus do ...@@ -109,6 +109,24 @@ describe HasStatus do
it { is_expected.to eq 'running' } it { is_expected.to eq 'running' }
end end
context 'when one status is a blocking manual action' do
let!(:statuses) do
[create(type, status: :failed),
create(type, status: :manual, allow_failure: false)]
end
it { is_expected.to eq 'manual' }
end
context 'when one status is a non-blocking manual action' do
let!(:statuses) do
[create(type, status: :failed),
create(type, status: :manual, allow_failure: true)]
end
it { is_expected.to eq 'failed' }
end
end end
context 'ci build statuses' do context 'ci build statuses' do
...@@ -218,6 +236,18 @@ describe HasStatus do ...@@ -218,6 +236,18 @@ describe HasStatus do
it_behaves_like 'not containing the job', status it_behaves_like 'not containing the job', status
end end
end end
describe '.manual' do
subject { CommitStatus.manual }
%i[manual].each do |status|
it_behaves_like 'containing the job', status
end
%i[failed success skipped canceled].each do |status|
it_behaves_like 'not containing the job', status
end
end
end end
describe '::DEFAULT_STATUS' do describe '::DEFAULT_STATUS' do
...@@ -225,4 +255,10 @@ describe HasStatus do ...@@ -225,4 +255,10 @@ describe HasStatus do
expect(described_class::DEFAULT_STATUS).to eq 'created' expect(described_class::DEFAULT_STATUS).to eq 'created'
end end
end end
describe '::BLOCKED_STATUS' do
it 'is a status manual' do
expect(described_class::BLOCKED_STATUS).to eq 'manual'
end
end
end end
require 'spec_helper' require 'spec_helper'
describe Ci::ProcessPipelineService, :services do describe Ci::ProcessPipelineService, '#execute', :services do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
...@@ -12,29 +12,31 @@ describe Ci::ProcessPipelineService, :services do ...@@ -12,29 +12,31 @@ describe Ci::ProcessPipelineService, :services do
project.add_developer(user) project.add_developer(user)
end end
describe '#execute' do context 'when simple pipeline is defined' do
context 'start queuing next builds' do
before do before do
create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage_idx: 0) create_build('linux', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage_idx: 0) create_build('mac', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'rspec', stage_idx: 1) create_build('rspec', stage_idx: 1)
create(:ci_build, :created, pipeline: pipeline, name: 'rubocop', stage_idx: 1) create_build('rubocop', stage_idx: 1)
create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage_idx: 2) create_build('deploy', stage_idx: 2)
end end
it 'processes a pipeline' do it 'processes a pipeline' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
succeed_pending succeed_pending
expect(builds.success.count).to eq(2)
expect(builds.success.count).to eq(2)
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
succeed_pending succeed_pending
expect(builds.success.count).to eq(4)
expect(builds.success.count).to eq(4)
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
succeed_pending succeed_pending
expect(builds.success.count).to eq(5)
expect(builds.success.count).to eq(5)
expect(process_pipeline).to be_falsey expect(process_pipeline).to be_falsey
end end
...@@ -49,254 +51,369 @@ describe Ci::ProcessPipelineService, :services do ...@@ -49,254 +51,369 @@ describe Ci::ProcessPipelineService, :services do
context 'custom stage with first job allowed to fail' do context 'custom stage with first job allowed to fail' do
before do before do
create(:ci_build, :created, pipeline: pipeline, name: 'clean_job', stage_idx: 0, allow_failure: true) create_build('clean_job', stage_idx: 0, allow_failure: true)
create(:ci_build, :created, pipeline: pipeline, name: 'test_job', stage_idx: 1, allow_failure: true) create_build('test_job', stage_idx: 1, allow_failure: true)
end end
it 'automatically triggers a next stage when build finishes' do it 'automatically triggers a next stage when build finishes' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(builds.pluck(:status)).to contain_exactly('pending') expect(builds_statuses).to eq ['pending']
pipeline.builds.running_or_pending.each(&:drop) fail_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('failed', 'pending')
expect(builds_statuses).to eq %w(failed pending)
end end
end end
context 'properly creates builds when "when" is defined' do context 'when optional manual actions are defined' do
before do before do
create(:ci_build, :created, pipeline: pipeline, name: 'build', stage_idx: 0) create_build('build', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'test', stage_idx: 1) create_build('test', stage_idx: 1)
create(:ci_build, :created, pipeline: pipeline, name: 'test_failure', stage_idx: 2, when: 'on_failure') create_build('test_failure', stage_idx: 2, when: 'on_failure')
create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage_idx: 3) create_build('deploy', stage_idx: 3)
create(:ci_build, :created, pipeline: pipeline, name: 'production', stage_idx: 3, when: 'manual') create_build('production', stage_idx: 3, when: 'manual', allow_failure: true)
create(:ci_build, :created, pipeline: pipeline, name: 'cleanup', stage_idx: 4, when: 'always') create_build('cleanup', stage_idx: 4, when: 'always')
create(:ci_build, :created, pipeline: pipeline, name: 'clear cache', stage_idx: 4, when: 'manual') create_build('clear:cache', stage_idx: 4, when: 'manual', allow_failure: true)
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.pluck(:name)).to contain_exactly('build') expect(builds_names).to eq ['build']
expect(builds.pluck(:status)).to contain_exactly('pending') expect(builds_statuses).to eq ['pending']
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test') succeed_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy') expect(builds_names).to eq %w(build test)
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'pending') expect(builds_statuses).to eq %w(success pending)
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup') succeed_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
pipeline.builds.running_or_pending.each(&:success) expect(builds_names).to eq %w(build test deploy production)
expect(builds_statuses).to eq %w(success success pending manual)
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success') succeed_running_or_pending
pipeline.reload
expect(pipeline.status).to eq('success') expect(builds_names).to eq %w(build test deploy production cleanup clear:cache)
expect(builds_statuses).to eq %w(success success success manual pending manual)
succeed_running_or_pending
expect(builds_statuses).to eq %w(success success success manual success manual)
expect(pipeline.reload.status).to eq 'success'
end end
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.pluck(:name)).to contain_exactly('build') expect(builds_names).to eq ['build']
expect(builds.pluck(:status)).to contain_exactly('pending') expect(builds_statuses).to eq ['pending']
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test') succeed_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
pipeline.builds.running_or_pending.each(&:drop)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure') expect(builds_names).to eq %w(build test)
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending') expect(builds_statuses).to eq %w(success pending)
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') fail_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
pipeline.builds.running_or_pending.each(&:success) expect(builds_names).to eq %w(build test test_failure)
expect(builds_statuses).to eq %w(success failed pending)
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success') succeed_running_or_pending
pipeline.reload
expect(pipeline.status).to eq('failed') expect(builds_names).to eq %w(build test test_failure cleanup)
expect(builds_statuses).to eq %w(success failed success pending)
succeed_running_or_pending
expect(builds_statuses).to eq %w(success failed success success)
expect(pipeline.reload.status).to eq 'failed'
end end
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.pluck(:name)).to contain_exactly('build') expect(builds_names).to eq ['build']
expect(builds.pluck(:status)).to contain_exactly('pending') expect(builds_statuses).to eq ['pending']
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test') succeed_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
pipeline.builds.running_or_pending.each(&:drop)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure') expect(builds_names).to eq %w(build test)
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending') expect(builds_statuses).to eq %w(success pending)
pipeline.builds.running_or_pending.each(&:drop)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') fail_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') expect(builds_names).to eq %w(build test test_failure)
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success') expect(builds_statuses).to eq %w(success failed pending)
pipeline.reload
expect(pipeline.status).to eq('failed') fail_running_or_pending
expect(builds_names).to eq %w(build test test_failure cleanup)
expect(builds_statuses).to eq %w(success failed failed pending)
succeed_running_or_pending
expect(builds_names).to eq %w(build test test_failure cleanup)
expect(builds_statuses).to eq %w(success failed failed success)
expect(pipeline.reload.status).to eq('failed')
end end
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.pluck(:name)).to contain_exactly('build') expect(builds_names).to eq ['build']
expect(builds.pluck(:status)).to contain_exactly('pending') expect(builds_statuses).to eq ['pending']
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test') succeed_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy') expect(builds_names).to eq %w(build test)
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'pending') expect(builds_statuses).to eq %w(success pending)
pipeline.builds.running_or_pending.each(&:drop)
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup') succeed_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
pipeline.builds.running_or_pending.each(&:success)
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success') expect(builds_names).to eq %w(build test deploy production)
pipeline.reload expect(builds_statuses).to eq %w(success success pending manual)
expect(pipeline.status).to eq('failed')
fail_running_or_pending
expect(builds_names).to eq %w(build test deploy production cleanup)
expect(builds_statuses).to eq %w(success success failed manual pending)
succeed_running_or_pending
expect(builds_statuses).to eq %w(success success failed manual success)
expect(pipeline.reload).to be_failed
end end
end end
context 'when build is canceled in the second stage' do context 'when build is canceled in the second stage' do
it 'does not schedule builds after build has been canceled' do it 'does not schedule builds after build has been canceled' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(builds.pluck(:name)).to contain_exactly('build') expect(builds_names).to eq ['build']
expect(builds.pluck(:status)).to contain_exactly('pending') expect(builds_statuses).to eq ['pending']
pipeline.builds.running_or_pending.each(&:success)
succeed_running_or_pending
expect(builds.running_or_pending).not_to be_empty expect(builds.running_or_pending).not_to be_empty
expect(builds_names).to eq %w(build test)
expect(builds_statuses).to eq %w(success pending)
expect(builds.pluck(:name)).to contain_exactly('build', 'test') cancel_running_or_pending
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
pipeline.builds.running_or_pending.each(&:cancel)
expect(builds.running_or_pending).to be_empty expect(builds.running_or_pending).to be_empty
expect(pipeline.reload.status).to eq('canceled') expect(builds_names).to eq %w[build test]
expect(builds_statuses).to eq %w[success canceled]
expect(pipeline.reload).to be_canceled
end end
end end
context 'when listing manual actions' do context 'when listing optional manual actions' do
it 'returns only for skipped builds' do it 'returns only for skipped builds' do
# currently all builds are created # currently all builds are created
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(manual_actions).to be_empty expect(manual_actions).to be_empty
# succeed stage build # succeed stage build
pipeline.builds.running_or_pending.each(&:success) succeed_running_or_pending
expect(manual_actions).to be_empty expect(manual_actions).to be_empty
# succeed stage test # succeed stage test
pipeline.builds.running_or_pending.each(&:success) succeed_running_or_pending
expect(manual_actions).to be_one # production expect(manual_actions).to be_one # production
# succeed stage deploy # succeed stage deploy
pipeline.builds.running_or_pending.each(&:success) succeed_running_or_pending
expect(manual_actions).to be_many # production and clear cache expect(manual_actions).to be_many # production and clear cache
end end
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
before do before do
builds create_build('build', stage_idx: 0, when: 'manual', allow_failure: true)
process_pipeline create_build('check', stage_idx: 1)
builds.each(&:reload) create_build('test', stage_idx: 2)
end
context 'when first stage has only manual jobs' do process_pipeline
let(:builds) do
[create_build('build', 0, 'manual'),
create_build('check', 1),
create_build('test', 2)]
end end
it 'starts from the second stage' do it 'starts from the second stage' do
expect(builds.map(&:status)).to eq(%w[skipped pending created]) expect(all_builds_statuses).to eq %w[manual pending created]
end end
end end
context 'when second stage has only manual jobs' do context 'when second stage has only optional manual actions' do
let(:builds) do before do
[create_build('check', 0), create_build('check', stage_idx: 0)
create_build('build', 1, 'manual'), create_build('build', stage_idx: 1, when: 'manual', allow_failure: true)
create_build('test', 2)] create_build('test', stage_idx: 2)
process_pipeline
end end
it 'skips second stage and continues on third stage' do it 'skips second stage and continues on third stage' do
expect(builds.map(&:status)).to eq(%w[pending created created]) expect(all_builds_statuses).to eq(%w[pending created created])
builds.first.success builds.first.success
builds.each(&:reload)
expect(builds.map(&:status)).to eq(%w[success skipped pending]) expect(all_builds_statuses).to eq(%w[success manual pending])
end
end
end
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
context 'when first stage succeeds' do
it 'blocks pipeline on stage with first manual action' do
process_pipeline
expect(builds_names).to eq %w[code:test]
expect(builds_statuses).to eq %w[pending]
expect(pipeline.reload.status).to eq 'pending'
succeed_running_or_pending
expect(builds_names).to eq %w[code:test staging:deploy]
expect(builds_statuses).to eq %w[success manual]
expect(pipeline.reload).to be_manual
end
end
context 'when first stage fails' do
it 'does not take blocking action into account' do
process_pipeline
expect(builds_names).to eq %w[code:test]
expect(builds_statuses).to eq %w[pending]
expect(pipeline.reload.status).to eq 'pending'
fail_running_or_pending
expect(builds_names).to eq %w[code:test production:test]
expect(builds_statuses).to eq %w[failed pending]
succeed_running_or_pending
expect(builds_statuses).to eq %w[failed success]
expect(pipeline.reload).to be_failed
end
end
context 'when pipeline is promoted sequentially up to the end' do
it 'properly processes entire pipeline' do
process_pipeline
expect(builds_names).to eq %w[code:test]
expect(builds_statuses).to eq %w[pending]
succeed_running_or_pending
expect(builds_names).to eq %w[code:test staging:deploy]
expect(builds_statuses).to eq %w[success manual]
expect(pipeline.reload).to be_manual
play_manual_action('staging:deploy')
expect(builds_statuses).to eq %w[success pending]
succeed_running_or_pending
expect(builds_names).to eq %w[code:test staging:deploy staging:test]
expect(builds_statuses).to eq %w[success success pending]
succeed_running_or_pending
expect(builds_names).to eq %w[code:test staging:deploy staging:test
production:deploy]
expect(builds_statuses).to eq %w[success success success manual]
expect(pipeline.reload).to be_manual
expect(pipeline.reload).to be_blocked
expect(pipeline.reload).not_to be_active
expect(pipeline.reload).not_to be_complete
play_manual_action('production:deploy')
expect(builds_statuses).to eq %w[success success success pending]
expect(pipeline.reload).to be_running
succeed_running_or_pending
expect(builds_names).to eq %w[code:test staging:deploy staging:test
production:deploy production:test]
expect(builds_statuses).to eq %w[success success success success pending]
expect(pipeline.reload).to be_running
succeed_running_or_pending
expect(builds_names).to eq %w[code:test staging:deploy staging:test
production:deploy production:test]
expect(builds_statuses).to eq %w[success success success success success]
expect(pipeline.reload).to be_success
end
end end
end end
context 'when second stage has only on_failure jobs' do context 'when second stage has only on_failure jobs' do
let(:builds) do before do
[create_build('check', 0), create_build('check', stage_idx: 0)
create_build('build', 1, 'on_failure'), create_build('build', stage_idx: 1, when: 'on_failure')
create_build('test', 2)] create_build('test', stage_idx: 2)
process_pipeline
end end
it 'skips second stage and continues on third stage' do it 'skips second stage and continues on third stage' do
expect(builds.map(&:status)).to eq(%w[pending created created]) expect(all_builds_statuses).to eq(%w[pending created created])
builds.first.success builds.first.success
builds.each(&:reload)
expect(builds.map(&:status)).to eq(%w[success skipped pending]) expect(all_builds_statuses).to eq(%w[success skipped pending])
end
end end
end end
context 'when failed build in the middle stage is retried' do context 'when failed build in the middle stage is retried' do
context 'when failed build is the only unsuccessful build in the stage' do context 'when failed build is the only unsuccessful build in the stage' do
before do before do
create(:ci_build, :created, pipeline: pipeline, name: 'build:1', stage_idx: 0) create_build('build:1', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'build:2', stage_idx: 0) create_build('build:2', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'test:1', stage_idx: 1) create_build('test:1', stage_idx: 1)
create(:ci_build, :created, pipeline: pipeline, name: 'test:2', stage_idx: 1) create_build('test:2', stage_idx: 1)
create(:ci_build, :created, pipeline: pipeline, name: 'deploy:1', stage_idx: 2) create_build('deploy:1', stage_idx: 2)
create(:ci_build, :created, pipeline: pipeline, name: 'deploy:2', stage_idx: 2) create_build('deploy:2', stage_idx: 2)
end end
it 'does trigger builds in the next stage' do it 'does trigger builds in the next stage' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(builds.pluck(:name)).to contain_exactly('build:1', 'build:2') expect(builds_names).to eq ['build:1', 'build:2']
pipeline.builds.running_or_pending.each(&:success) succeed_running_or_pending
expect(builds.pluck(:name)) expect(builds_names).to eq ['build:1', 'build:2', 'test:1', 'test:2']
.to contain_exactly('build:1', 'build:2', 'test:1', 'test:2')
pipeline.builds.find_by(name: 'test:1').success pipeline.builds.find_by(name: 'test:1').success
pipeline.builds.find_by(name: 'test:2').drop pipeline.builds.find_by(name: 'test:2').drop
expect(builds.pluck(:name)) expect(builds_names).to eq ['build:1', 'build:2', 'test:1', 'test:2']
.to contain_exactly('build:1', 'build:2', 'test:1', 'test:2')
Ci::Build.retry(pipeline.builds.find_by(name: 'test:2'), user).success Ci::Build.retry(pipeline.builds.find_by(name: 'test:2'), user).success
expect(builds.pluck(:name)).to contain_exactly( expect(builds_names).to eq ['build:1', 'build:2', 'test:1', 'test:2',
'build:1', 'build:2', 'test:1', 'test:2', 'test:2', 'deploy:1', 'deploy:2') 'test:2', 'deploy:1', 'deploy:2']
end end
end end
end end
...@@ -312,8 +429,8 @@ describe Ci::ProcessPipelineService, :services do ...@@ -312,8 +429,8 @@ describe Ci::ProcessPipelineService, :services do
end end
before do before do
create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0) create_build('linux', stage: 'build', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0) create_build('mac', stage: 'build', stage_idx: 0)
end end
it 'processes the pipeline' do it 'processes the pipeline' do
...@@ -359,32 +476,54 @@ describe Ci::ProcessPipelineService, :services do ...@@ -359,32 +476,54 @@ describe Ci::ProcessPipelineService, :services do
expect(all_builds.count).to eq(4) expect(all_builds.count).to eq(4)
end end
end end
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end end
def all_builds def all_builds
pipeline.builds pipeline.builds.order(:stage_idx, :id)
end end
def builds def builds
all_builds.where.not(status: [:created, :skipped]) all_builds.where.not(status: [:created, :skipped])
end end
def process_pipeline def builds_names
described_class.new(pipeline.project, user).execute(pipeline) builds.pluck(:name)
end
def builds_statuses
builds.pluck(:status)
end
def all_builds_statuses
all_builds.pluck(:status)
end end
def succeed_pending def succeed_pending
builds.pending.update_all(status: 'success') builds.pending.update_all(status: 'success')
end end
def succeed_running_or_pending
pipeline.builds.running_or_pending.each(&:success)
end
def fail_running_or_pending
pipeline.builds.running_or_pending.each(&:drop)
end
def cancel_running_or_pending
pipeline.builds.running_or_pending.each(&:cancel)
end
def play_manual_action(name)
builds.find_by(name: name).play(user)
end
delegate :manual_actions, to: :pipeline delegate :manual_actions, to: :pipeline
def create_build(name, stage_idx, when_value = nil) def create_build(name, **opts)
create(:ci_build, create(:ci_build, :created, pipeline: pipeline, name: name, **opts)
:created,
pipeline: pipeline,
name: name,
stage_idx: stage_idx,
when: when_value)
end end
end end
...@@ -89,35 +89,74 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -89,35 +89,74 @@ describe Ci::RetryPipelineService, '#execute', :services do
end end
context 'when pipeline contains manual actions' do context 'when pipeline contains manual actions' do
context 'when there are optional manual actions only' do
context 'when there is a canceled manual action in first stage' do context 'when there is a canceled manual action in first stage' do
before do before do
create_build('rspec 1', :failed, 0) create_build('rspec 1', :failed, 0)
create_build('staging', :canceled, 0, :manual) create_build('staging', :canceled, 0, when: :manual, allow_failure: true)
create_build('rspec 2', :canceled, 1) create_build('rspec 2', :canceled, 1)
end end
it 'retries builds failed builds and marks subsequent for processing' do it 'retries failed builds and marks subsequent for processing' do
service.execute(pipeline) service.execute(pipeline)
expect(build('rspec 1')).to be_pending expect(build('rspec 1')).to be_pending
expect(build('staging')).to be_skipped expect(build('staging')).to be_manual
expect(build('rspec 2')).to be_created expect(build('rspec 2')).to be_created
expect(pipeline.reload).to be_running expect(pipeline.reload).to be_running
end end
end end
end
context 'when pipeline has blocking manual actions defined' do
context 'when pipeline retry should enqueue builds' do
before do
create_build('test', :failed, 0)
create_build('deploy', :canceled, 0, when: :manual, allow_failure: false)
create_build('verify', :canceled, 1)
end
it 'retries failed builds' do
service.execute(pipeline)
expect(build('test')).to be_pending
expect(build('deploy')).to be_manual
expect(build('verify')).to be_created
expect(pipeline.reload).to be_running
end
end
context 'when pipeline retry should block pipeline immediately' do
before do
create_build('test', :success, 0)
create_build('deploy:1', :success, 1, when: :manual, allow_failure: false)
create_build('deploy:2', :failed, 1, when: :manual, allow_failure: false)
create_build('verify', :canceled, 2)
end
it 'reprocesses blocking manual action and blocks pipeline' do
service.execute(pipeline)
expect(build('deploy:1')).to be_success
expect(build('deploy:2')).to be_manual
expect(build('verify')).to be_created
expect(pipeline.reload).to be_blocked
end
end
end
context 'when there is a skipped manual action in last stage' do context 'when there is a skipped manual action in last stage' do
before do before do
create_build('rspec 1', :canceled, 0) create_build('rspec 1', :canceled, 0)
create_build('rspec 2', :skipped, 0, :manual) create_build('rspec 2', :skipped, 0, when: :manual, allow_failure: true)
create_build('staging', :skipped, 1, :manual) create_build('staging', :skipped, 1, when: :manual, allow_failure: true)
end end
it 'retries canceled job and reprocesses manual actions' do it 'retries canceled job and reprocesses manual actions' do
service.execute(pipeline) service.execute(pipeline)
expect(build('rspec 1')).to be_pending expect(build('rspec 1')).to be_pending
expect(build('rspec 2')).to be_skipped expect(build('rspec 2')).to be_manual
expect(build('staging')).to be_created expect(build('staging')).to be_created
expect(pipeline.reload).to be_running expect(pipeline.reload).to be_running
end end
...@@ -126,7 +165,7 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -126,7 +165,7 @@ describe Ci::RetryPipelineService, '#execute', :services do
context 'when there is a created manual action in the last stage' do context 'when there is a created manual action in the last stage' do
before do before do
create_build('rspec 1', :canceled, 0) create_build('rspec 1', :canceled, 0)
create_build('staging', :created, 1, :manual) create_build('staging', :created, 1, when: :manual, allow_failure: true)
end end
it 'retries canceled job and does not update the manual action' do it 'retries canceled job and does not update the manual action' do
...@@ -141,14 +180,14 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -141,14 +180,14 @@ describe Ci::RetryPipelineService, '#execute', :services do
context 'when there is a created manual action in the first stage' do context 'when there is a created manual action in the first stage' do
before do before do
create_build('rspec 1', :canceled, 0) create_build('rspec 1', :canceled, 0)
create_build('staging', :created, 0, :manual) create_build('staging', :created, 0, when: :manual, allow_failure: true)
end end
it 'retries canceled job and skipps the manual action' do it 'retries canceled job and processes the manual action' do
service.execute(pipeline) service.execute(pipeline)
expect(build('rspec 1')).to be_pending expect(build('rspec 1')).to be_pending
expect(build('staging')).to be_skipped expect(build('staging')).to be_manual
expect(pipeline.reload).to be_running expect(pipeline.reload).to be_running
end end
end end
...@@ -183,13 +222,12 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -183,13 +222,12 @@ describe Ci::RetryPipelineService, '#execute', :services do
statuses.latest.find_by(name: name) statuses.latest.find_by(name: name)
end end
def create_build(name, status, stage_num, on = 'on_success') def create_build(name, status, stage_num, **opts)
create(:ci_build, name: name, create(:ci_build, name: name,
status: status, status: status,
stage: "stage_#{stage_num}", stage: "stage_#{stage_num}",
stage_idx: stage_num, stage_idx: stage_num,
when: on, pipeline: pipeline, **opts) do |build|
pipeline: pipeline) do |build|
pipeline.update_status pipeline.update_status
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