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
end
state_machine :status do
event :actionize do
transition created: :manual
end
after_transition any => [:pending] do |build|
build.run_after_commit do
BuildQueueWorker.perform_async(id)
......@@ -94,16 +98,21 @@ module Ci
.fabricate!
end
def manual?
self.when == 'manual'
end
def other_actions
pipeline.manual_actions.where.not(name: name)
end
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
def play(current_user)
......@@ -122,7 +131,7 @@ module Ci
end
def retryable?
project.builds_enabled? && commands.present? &&
project.builds_enabled? && has_commands? &&
(success? || failed? || canceled?)
end
......@@ -552,7 +561,7 @@ module Ci
]
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_MANUAL', value: 'true', public: true } if manual?
variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if action?
variables
end
......
......@@ -49,6 +49,10 @@ module Ci
transition any - [:canceled] => :canceled
end
event :block do
transition any - [:manual] => :manual
end
# IMPORTANT
# Do not add any operations to this state_machine
# Create a separate worker for each new operation
......@@ -321,6 +325,7 @@ module Ci
when 'failed' then drop
when 'canceled' then cancel
when 'skipped' then skip
when 'manual' then block
end
end
end
......
......@@ -29,9 +29,11 @@ class CommitStatus < ActiveRecord::Base
end
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 (?)",
false, all_state_names - [:failed, :canceled])
false, all_state_names - [:failed, :canceled, :manual])
end
scope :retried, -> { where.not(id: latest) }
......@@ -42,11 +44,11 @@ class CommitStatus < ActiveRecord::Base
state_machine :status do
event :enqueue do
transition [:created, :skipped] => :pending
transition [:created, :skipped, :manual] => :pending
end
event :process do
transition skipped: :created
transition [:skipped, :manual] => :created
end
event :run do
......@@ -66,7 +68,7 @@ class CommitStatus < ActiveRecord::Base
end
event :cancel do
transition [:created, :pending, :running] => :canceled
transition [:created, :pending, :running, :manual] => :canceled
end
before_transition created: [:pending, :running] do |commit_status|
......@@ -86,7 +88,7 @@ class CommitStatus < ActiveRecord::Base
commit_status.run_after_commit do
pipeline.try do |pipeline|
if complete?
if complete? || manual?
PipelineProcessWorker.perform_async(pipeline.id)
else
PipelineUpdateWorker.perform_async(pipeline.id)
......
......@@ -2,22 +2,21 @@ module HasStatus
extend ActiveSupport::Concern
DEFAULT_STATUS = 'created'.freeze
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped].freeze
STARTED_STATUSES = %w[running success failed skipped].freeze
BLOCKED_STATUS = 'manual'.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
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
def status_sql
scope = if respond_to?(:exclude_ignored)
exclude_ignored
else
all
end
scope = respond_to?(:exclude_ignored) ? exclude_ignored : all
builds = scope.select('count(*)').to_sql
created = scope.created.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
running = scope.running.select('count(*)').to_sql
skipped = scope.skipped.select('count(*)').to_sql
......@@ -30,7 +29,8 @@ module HasStatus
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
WHEN (#{running})+(#{pending})>0 THEN 'running'
WHEN (#{manual})>0 THEN 'manual'
ELSE 'failed'
END)"
end
......@@ -63,6 +63,7 @@ module HasStatus
state :success, value: 'success'
state :canceled, value: 'canceled'
state :skipped, value: 'skipped'
state :manual, value: 'manual'
end
scope :created, -> { where(status: 'created') }
......@@ -73,12 +74,13 @@ module HasStatus
scope :failed, -> { where(status: 'failed') }
scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
scope :cancelable, -> do
where(status: [:running, :pending, :created])
where(status: [:running, :pending, :created, :manual])
end
end
......@@ -94,6 +96,10 @@ module HasStatus
COMPLETED_STATUSES.include?(status)
end
def blocked?
BLOCKED_STATUS == status
end
private
def calculate_duration
......
......@@ -12,7 +12,7 @@ class BuildEntity < Grape::Entity
path_to(:retry_namespace_project_build, build)
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)
end
......
......@@ -22,6 +22,8 @@ module Ci
def process_stage(index)
current_status = status_for_prior_stages(index)
return if HasStatus::BLOCKED_STATUS == current_status
if HasStatus::COMPLETED_STATUSES.include?(current_status)
created_builds_in_stage(index).select do |build|
Gitlab::OptimisticLocking.retry_lock(build) do |subject|
......@@ -33,7 +35,7 @@ module Ci
def process_build(build, current_status)
if valid_statuses_for_when(build.when).include?(current_status)
build.enqueue
build.action? ? build.actionize : build.enqueue
true
else
build.skip
......@@ -49,6 +51,8 @@ module Ci
%w[failed]
when 'always'
%w[success failed skipped]
when 'manual'
%w[success]
else
[]
end
......
......@@ -46,7 +46,7 @@
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
- if build.manual?
- if build.action?
%span.label.label-info manual
- 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 @@
#
# 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
enable_extension "plpgsql"
......
......@@ -545,13 +545,30 @@ The above script will:
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
from pipeline, build, environment, and deployment views. You can execute the
same manual action multiple times.
from pipeline, build, environment, and deployment views.
An example usage of manual actions is deployment to production.
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
>
......
......@@ -58,7 +58,7 @@ module Ci
commands: job[:commands],
tag_list: job[:tags] || [],
name: job[:name].to_s,
allow_failure: job[:allow_failure] || false,
allow_failure: job[:ignore],
when: job[:when] || 'on_success',
environment: job[:environment_name],
coverage_regex: job[:coverage],
......
......@@ -104,6 +104,14 @@ module Gitlab
(before_script_value.to_a + script_value.to_a).join("\n")
end
def manual_action?
self.when == 'manual'
end
def ignored?
allow_failure.nil? ? manual_action? : allow_failure
end
private
def inherit!(deps)
......@@ -135,7 +143,8 @@ module Gitlab
environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil,
artifacts: artifacts_value,
after_script: after_script_value }
after_script: after_script_value,
ignore: ignored? }
end
end
end
......
......@@ -5,22 +5,10 @@ module Gitlab
class Play < SimpleDelegator
include Status::Extended
def text
'manual'
end
def label
'manual play action'
end
def icon
'icon_status_manual'
end
def group
'manual'
end
def has_action?
can?(user, :update_build, subject)
end
......
......@@ -5,22 +5,10 @@ module Gitlab
class Stop < SimpleDelegator
include Status::Extended
def text
'manual'
end
def label
'manual stop action'
end
def icon
'icon_status_manual'
end
def group
'manual'
end
def has_action?
can?(user, :update_build, subject)
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
started_at: build.started_at,
finished_at: build.finished_at,
when: build.when,
manual: build.manual?,
manual: build.action?,
user: build.user.try(:hook_attrs),
runner: build.runner && runner_hook_attrs(build.runner),
artifacts_file: {
......
......@@ -57,7 +57,7 @@ FactoryGirl.define do
end
trait :manual do
status 'skipped'
status 'manual'
self.when 'manual'
end
......@@ -71,8 +71,11 @@ FactoryGirl.define do
allow_failure true
end
trait :ignored do
allowed_to_fail
end
trait :playable do
skipped
manual
end
......
......@@ -35,6 +35,10 @@ FactoryGirl.define do
status 'created'
end
trait :manual do
status 'manual'
end
after(:build) do |build, evaluator|
build.project = build.pipeline.project
end
......
......@@ -13,7 +13,7 @@ feature 'Environment', :feature do
feature 'environment details page' do
given!(:environment) { create(:environment, project: project) }
given!(:deployment) { }
given!(:manual) { }
given!(:action) { }
before do
visit_environment(environment)
......@@ -69,17 +69,23 @@ feature 'Environment', :feature do
end
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
expect(page).to have_link(manual.name.humanize)
expect(page).to have_link(action.name.humanize)
end
scenario 'does allow to play manual action' do
expect(manual).to be_skipped
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
expect(page).to have_content(manual.name)
expect(manual.reload).to be_pending
expect(action).to be_manual
expect { click_link(action.name.humanize) }
.not_to change { Ci::Pipeline.count }
expect(page).to have_content(action.name)
expect(action.reload).to be_pending
end
context 'with external_url' do
......@@ -130,8 +136,16 @@ feature 'Environment', :feature do
context 'when environment is available' do
context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
given(:action) do
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
expect(page).to have_link('Stop')
......
......@@ -12,7 +12,7 @@ feature 'Environments page', :feature, :js do
given!(:environment) { }
given!(:deployment) { }
given!(:manual) { }
given!(:action) { }
before do
visit_environments(project)
......@@ -90,7 +90,7 @@ feature 'Environments page', :feature, :js do
given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:manual) do
given(:action) do
create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production')
end
......@@ -102,19 +102,19 @@ feature 'Environments page', :feature, :js do
scenario 'does show a play button' do
find('.js-dropdown-play-icon-container').click
expect(page).to have_content(manual.name.humanize)
expect(page).to have_content(action.name.humanize)
end
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
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 }
expect(manual.reload).to be_pending
expect(action.reload).to be_pending
end
scenario 'does show build name and id' do
......@@ -144,8 +144,15 @@ feature 'Environments page', :feature, :js do
end
context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
given(:action) do
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
expect(page).to have_selector('.stop-env-link')
......
......@@ -15,9 +15,9 @@ module Ci
end
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
let(:config) do
YAML.dump(rspec: { script: 'rspec',
......@@ -30,6 +30,56 @@ module Ci
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
describe "#builds_for_ref" do
......
......@@ -155,6 +155,7 @@ describe Gitlab::Ci::Config::Entry::Global do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: { VAR: 'value' },
ignore: false,
after_script: ['make clean'] },
spinach: { name: :spinach,
before_script: [],
......@@ -165,6 +166,7 @@ describe Gitlab::Ci::Config::Entry::Global do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: {},
ignore: false,
after_script: ['make clean'] },
)
end
......
......@@ -144,6 +144,7 @@ describe Gitlab::Ci::Config::Entry::Job do
script: %w[rspec],
commands: "ls\npwd\nrspec",
stage: 'test',
ignore: false,
after_script: %w[cleanup])
end
end
......@@ -159,4 +160,82 @@ describe Gitlab::Ci::Config::Entry::Job do
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
......@@ -62,10 +62,12 @@ describe Gitlab::Ci::Config::Entry::Jobs do
rspec: { name: :rspec,
script: %w[rspec],
commands: 'rspec',
ignore: false,
stage: 'test' },
spinach: { name: :spinach,
script: %w[spinach],
commands: 'spinach',
ignore: false,
stage: 'test' })
end
end
......
......@@ -192,7 +192,7 @@ describe Gitlab::Ci::Status::Build::Factory do
let(:build) { create(:ci_build, :playable) }
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
it 'matches correct extended statuses' do
......@@ -200,12 +200,13 @@ describe Gitlab::Ci::Status::Build::Factory do
.to eq [Gitlab::Ci::Status::Build::Play]
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
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual play action'
expect(status).to have_details
......@@ -218,7 +219,7 @@ describe Gitlab::Ci::Status::Build::Factory do
let(:build) { create(:ci_build, :playable, :teardown_environment) }
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
it 'matches correct extended statuses' do
......@@ -226,12 +227,13 @@ describe Gitlab::Ci::Status::Build::Factory do
.to eq [Gitlab::Ci::Status::Build::Stop]
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
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual stop action'
expect(status).to have_details
......
......@@ -6,22 +6,10 @@ describe Gitlab::Ci::Status::Build::Play do
subject { described_class.new(status) }
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do
it { expect(subject.label).to eq 'manual play 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
describe 'action details' do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
......
......@@ -8,22 +8,10 @@ describe Gitlab::Ci::Status::Build::Stop do
described_class.new(status)
end
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do
it { expect(subject.label).to eq 'manual stop 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
describe 'action details' do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
......
......@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Canceled do
end
describe '#text' do
it { expect(subject.label).to eq 'canceled' }
it { expect(subject.text).to eq 'canceled' }
end
describe '#label' do
......
......@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Created do
end
describe '#text' do
it { expect(subject.label).to eq 'created' }
it { expect(subject.text).to eq 'created' }
end
describe '#label' do
......
......@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Failed do
end
describe '#text' do
it { expect(subject.label).to eq 'failed' }
it { expect(subject.text).to eq 'failed' }
end
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
end
describe '#text' do
it { expect(subject.label).to eq 'pending' }
it { expect(subject.text).to eq 'pending' }
end
describe '#label' do
......
......@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Running do
end
describe '#text' do
it { expect(subject.label).to eq 'running' }
it { expect(subject.text).to eq 'running' }
end
describe '#label' do
......
......@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Skipped do
end
describe '#text' do
it { expect(subject.label).to eq 'skipped' }
it { expect(subject.text).to eq 'skipped' }
end
describe '#label' do
......
......@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Success do
end
describe '#text' do
it { expect(subject.label).to eq 'passed' }
it { expect(subject.text).to eq 'passed' }
end
describe '#label' do
......
......@@ -20,6 +20,30 @@ describe Ci::Build, :models do
it { is_expected.to validate_presence_of :ref }
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
subject { build.any_runners_online? }
......@@ -587,13 +611,21 @@ describe Ci::Build, :models do
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
context 'and build status is failed' do
before do
build.status = 'failed'
end
it { is_expected.to be_truthy }
end
context 'when build is a manual action' do
before do
build.status = 'manual'
end
it { is_expected.to be_falsey }
end
end
end
......@@ -682,12 +714,12 @@ describe Ci::Build, :models do
end
end
describe '#manual?' do
describe '#action?' do
before do
build.update(when: value)
end
subject { build.manual? }
subject { build.action? }
context 'when is set to manual' do
let(:value) { 'manual' }
......@@ -703,14 +735,50 @@ describe Ci::Build, :models do
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
context 'when build has tags' do
subject { create(:ci_build, tag_list: ['tag']) }
it { is_expected.to have_tags }
end
context 'when build does not have tags' do
subject { create(:ci_build, tag_list: []) }
it { is_expected.not_to have_tags }
end
end
......
......@@ -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 :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
context 'commit.sha can not start with 00000000' do
before do
......@@ -635,6 +643,14 @@ describe Ci::Pipeline, models: true do
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
let(:pipeline) { create(:ci_pipeline, status: :success) }
......
......@@ -158,7 +158,7 @@ describe CommitStatus, :models do
end
end
describe '.exclude_ignored' do
describe '.after_stage' do
subject { described_class.after_stage(0) }
let(:statuses) do
......@@ -185,11 +185,32 @@ describe CommitStatus, :models 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: 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
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
......
......@@ -109,6 +109,24 @@ describe HasStatus do
it { is_expected.to eq 'running' }
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
context 'ci build statuses' do
......@@ -218,6 +236,18 @@ describe HasStatus do
it_behaves_like 'not containing the job', status
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
describe '::DEFAULT_STATUS' do
......@@ -225,4 +255,10 @@ describe HasStatus do
expect(described_class::DEFAULT_STATUS).to eq 'created'
end
end
describe '::BLOCKED_STATUS' do
it 'is a status manual' do
expect(described_class::BLOCKED_STATUS).to eq 'manual'
end
end
end
......@@ -89,35 +89,74 @@ describe Ci::RetryPipelineService, '#execute', :services do
end
context 'when pipeline contains manual actions' do
context 'when there is a canceled manual action in first stage' do
before do
create_build('rspec 1', :failed, 0)
create_build('staging', :canceled, 0, :manual)
create_build('rspec 2', :canceled, 1)
context 'when there are optional manual actions only' do
context 'when there is a canceled manual action in first stage' do
before do
create_build('rspec 1', :failed, 0)
create_build('staging', :canceled, 0, when: :manual, allow_failure: true)
create_build('rspec 2', :canceled, 1)
end
it 'retries failed builds and marks subsequent for processing' do
service.execute(pipeline)
expect(build('rspec 1')).to be_pending
expect(build('staging')).to be_manual
expect(build('rspec 2')).to be_created
expect(pipeline.reload).to be_running
end
end
end
it 'retries builds failed builds and marks subsequent for processing' do
service.execute(pipeline)
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
expect(build('rspec 1')).to be_pending
expect(build('staging')).to be_skipped
expect(build('rspec 2')).to be_created
expect(pipeline.reload).to be_running
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
before do
create_build('rspec 1', :canceled, 0)
create_build('rspec 2', :skipped, 0, :manual)
create_build('staging', :skipped, 1, :manual)
create_build('rspec 2', :skipped, 0, when: :manual, allow_failure: true)
create_build('staging', :skipped, 1, when: :manual, allow_failure: true)
end
it 'retries canceled job and reprocesses manual actions' do
service.execute(pipeline)
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(pipeline.reload).to be_running
end
......@@ -126,7 +165,7 @@ describe Ci::RetryPipelineService, '#execute', :services do
context 'when there is a created manual action in the last stage' do
before do
create_build('rspec 1', :canceled, 0)
create_build('staging', :created, 1, :manual)
create_build('staging', :created, 1, when: :manual, allow_failure: true)
end
it 'retries canceled job and does not update the manual action' do
......@@ -141,14 +180,14 @@ describe Ci::RetryPipelineService, '#execute', :services do
context 'when there is a created manual action in the first stage' do
before do
create_build('rspec 1', :canceled, 0)
create_build('staging', :created, 0, :manual)
create_build('staging', :created, 0, when: :manual, allow_failure: true)
end
it 'retries canceled job and skipps the manual action' do
it 'retries canceled job and processes the manual action' do
service.execute(pipeline)
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
end
end
......@@ -183,13 +222,12 @@ describe Ci::RetryPipelineService, '#execute', :services do
statuses.latest.find_by(name: name)
end
def create_build(name, status, stage_num, on = 'on_success')
def create_build(name, status, stage_num, **opts)
create(:ci_build, name: name,
status: status,
stage: "stage_#{stage_num}",
stage_idx: stage_num,
when: on,
pipeline: pipeline) do |build|
pipeline: pipeline, **opts) do |build|
pipeline.update_status
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