Commit b5f3aea1 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'feature/gb/cross-project-pipeline-trigger' into 'master'

Cross-project pipeline triggers

Closes #8997

See merge request gitlab-org/gitlab-ee!9107
parents 9ebfeebc 648f6d43
......@@ -2,11 +2,13 @@
module Ci
class Bridge < CommitStatus
include Ci::Processable
include Importable
include AfterCommitQueue
include Gitlab::Utils::StrongMemoize
belongs_to :project
belongs_to :trigger_request
validates :ref, presence: true
def self.retry(bridge, current_user)
......@@ -23,6 +25,21 @@ module Ci
.fabricate!
end
def schedulable?
false
end
def action?
false
end
def artifacts?
false
end
def expanded_environment_name
end
def predefined_variables
raise NotImplementedError
end
......@@ -30,6 +47,10 @@ module Ci
def execute_hooks
raise NotImplementedError
end
def to_partial_path
'projects/generic_commit_statuses/generic_commit_status'
end
end
end
......
......@@ -3,6 +3,7 @@
module Ci
class Build < CommitStatus
prepend ArtifactMigratable
include Ci::Processable
include TokenAuthenticatable
include AfterCommitQueue
include ObjectStorage::BackgroundMove
......@@ -638,10 +639,6 @@ module Ci
super || project.try(:build_coverage_regex)
end
def when
read_attribute(:when) || 'on_success'
end
def options
read_metadata_attribute(:options, :config_options, {})
end
......
......@@ -25,6 +25,8 @@ module Ci
has_many :stages, -> { order(position: :asc) }, inverse_of: :pipeline
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
has_many :processables, -> { processables },
class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
has_many :variables, class_name: 'Ci::PipelineVariable'
......
......@@ -14,6 +14,7 @@ module Ci
has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :builds, foreign_key: :stage_id
has_many :bridges, foreign_key: :stage_id
with_options unless: :importing? do
validates :project, presence: true
......
......@@ -43,6 +43,7 @@ class CommitStatus < ActiveRecord::Base
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
scope :after_stage, -> (index) { where('stage_idx > ?', index) }
scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
# We use `CommitStatusEnums.failure_reasons` here so that EE can more easily
# extend this `Hash` with new values.
......
# frozen_string_literal: true
module Ci
##
# This module implements methods that need to be implemented by CI/CD
# entities that are supposed to go through pipeline processing
# services.
#
#
module Processable
def schedulable?
raise NotImplementedError
end
def action?
raise NotImplementedError
end
def when
read_attribute(:when) || 'on_success'
end
def expanded_environment_name
raise NotImplementedError
end
end
end
......@@ -10,7 +10,7 @@ module Ci
update_retried
new_builds =
stage_indexes_of_created_builds.map do |index|
stage_indexes_of_created_processables.map do |index|
process_stage(index)
end
......@@ -27,7 +27,7 @@ module Ci
return if HasStatus::BLOCKED_STATUS.include?(current_status)
if HasStatus::COMPLETED_STATUSES.include?(current_status)
created_builds_in_stage(index).select do |build|
created_processables_in_stage(index).select do |build|
Gitlab::OptimisticLocking.retry_lock(build) do |subject|
Ci::ProcessBuildService.new(project, @user)
.execute(build, current_status)
......@@ -43,19 +43,19 @@ module Ci
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def stage_indexes_of_created_builds
created_builds.order(:stage_idx).pluck('distinct stage_idx')
def stage_indexes_of_created_processables
created_processables.order(:stage_idx).pluck('distinct stage_idx')
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def created_builds_in_stage(index)
created_builds.where(stage_idx: index)
def created_processables_in_stage(index)
created_processables.where(stage_idx: index)
end
# rubocop: enable CodeReuse/ActiveRecord
def created_builds
pipeline.builds.created
def created_processables
pipeline.processables.created
end
# This method is for compatibility and data consistency and should be removed with 9.3 version of GitLab
......
......@@ -11,7 +11,8 @@ module EE
def failure_reasons
super.merge(protected_environment_failure: 1_000,
insufficient_bridge_permissions: 1_001,
invalid_bridge_trigger: 1_002)
downstream_bridge_project_not_found: 1_002,
invalid_bridge_trigger: 1_003)
end
end
end
......
......@@ -7,6 +7,7 @@ module EE
EE_CALLOUT_FAILURE_MESSAGES = const_get(:CALLOUT_FAILURE_MESSAGES).merge(
protected_environment_failure: 'The environment this job is deploying to is protected. Only users with permission may successfully run this job.',
insufficient_bridge_permissions: 'This job could not be executed because of insufficient permissions to create a downstream pipeline.',
downstream_bridge_project_not_found: 'This job could not be executed because downstream bridge project could not be found.',
invalid_bridge_trigger: 'This job could not be executed because downstream pipeline trigger definition is invalid.'
).freeze
......
......@@ -8,7 +8,7 @@ module Ci
@bridge = bridge
unless target_project_exists?
return bridge.drop!(:invalid_bridge_trigger)
return bridge.drop!(:downstream_bridge_project_not_found)
end
if target_project == project
......
# frozen_string_literal: true
module EE
module Gitlab
module Ci
module Config
module Entry
##
# Entry that represents a CI/CD Bridge job that is responsible for
# defining a downstream project trigger.
#
class Bridge < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[trigger stage allow_failure only except
when extends].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
validates :config, presence: true
validates :trigger, presence: true
validates :name, presence: true
validates :name, type: Symbol
with_options allow_nil: true do
validates :when,
inclusion: { in: %w[on_success on_failure always],
message: 'should be on_success, on_failure or always' }
validates :extends, type: String
end
end
entry :trigger, ::EE::Gitlab::Ci::Config::Entry::Trigger,
description: 'CI/CD Bridge downstream trigger definition.'
entry :stage, ::Gitlab::Ci::Config::Entry::Stage,
description: 'Pipeline stage this job will be executed into.'
entry :only, ::Gitlab::Ci::Config::Entry::Policy,
description: 'Refs policy this job will be executed for.',
default: ::Gitlab::Ci::Config::Entry::Policy::DEFAULT_ONLY
entry :except, ::Gitlab::Ci::Config::Entry::Policy,
description: 'Refs policy this job will be executed for.'
helpers(*ALLOWED_KEYS)
attributes(*ALLOWED_KEYS)
def name
@metadata[:name]
end
def value
{ name: name,
trigger: trigger_value,
ignore: !!allow_failure,
stage: stage_value,
when: when_value,
extends: extends_value,
only: only_value,
except: except_value }.compact
end
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module Gitlab
module Ci
module Config
module Entry
module Jobs
extend ::Gitlab::Utils::Override
override :node_type
def node_type(name)
if bridge?(name)
::EE::Gitlab::Ci::Config::Entry::Bridge
else
super
end
end
def bridge?(name)
config.fetch(name).yield_self do |value|
value.is_a?(Hash) && value.key?(:trigger) &&
cross_project_triggers_enabled?
end
end
def cross_project_triggers_enabled?
::Feature.enabled?(:cross_project_pipeline_triggers, default_enabled: false)
end
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module Gitlab
module Ci
module Config
module Entry
##
# Entry that represents a cross-project downstream trigger.
#
class Trigger < ::Gitlab::Config::Entry::Simplifiable
strategy :SimpleTrigger, if: -> (config) { config.is_a?(String) }
strategy :ComplexTrigger, if: -> (config) { config.is_a?(Hash) }
class SimpleTrigger < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
validations { validates :config, presence: true }
def value
{ project: @config }
end
end
class ComplexTrigger < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[project branch].freeze
attributes :project, :branch
validations do
validates :config, presence: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :project, presence: true
validates :branch, type: String, allow_nil: true
end
end
class UnknownStrategy < ::Gitlab::Config::Entry::Node
def errors
["#{location} has to be either a string or a hash"]
end
end
end
end
end
end
end
end
......@@ -12,6 +12,7 @@ module EE
EE_REASONS = const_get(:REASONS).merge(
protected_environment_failure: 'protected environment failure',
invalid_bridge_trigger: 'downstream pipeline trigger definition is invalid',
downstream_bridge_project_not_found: 'downstream project could not be found',
insufficient_bridge_permissions: 'no permissions to trigger downstream pipeline'
).freeze
......
require 'fast_spec_helper'
require_dependency 'active_model'
describe EE::Gitlab::Ci::Config::Entry::Bridge do
subject { described_class.new(config, name: :my_trigger) }
before do
subject.compose!
end
context 'when trigger config is a non-empty string' do
let(:config) { { trigger: 'some/project' } }
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'is returns a bridge job configuration' do
expect(subject.value).to eq(name: :my_trigger,
trigger: { project: 'some/project' },
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] })
end
end
end
context 'when bridge trigger is a hash' do
let(:config) do
{ trigger: { project: 'some/project', branch: 'feature' } }
end
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'is returns a bridge job configuration hash' do
expect(subject.value).to eq(name: :my_trigger,
trigger: { project: 'some/project',
branch: 'feature' },
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] })
end
end
end
context 'when bridge configuration contains all supported keys' do
let(:config) do
{ trigger: { project: 'some/project', branch: 'feature' },
when: 'always',
extends: '.some-key',
stage: 'deploy',
only: { variables: %w[$SOMEVARIABLE] },
except: { refs: %w[feature] } }
end
it { is_expected.to be_valid }
end
context 'when trigger config is nil' do
let(:config) { { trigger: nil } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about empty trigger config' do
expect(subject.errors.first).to match /can't be blank/
end
end
end
context 'when bridge config contains unknown keys' do
let(:config) { { unknown: 123 } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about unknown config key' do
expect(subject.errors.first)
.to match /config contains unknown keys: unknown/
end
end
end
context 'when bridge config contains build-specific attributes' do
let(:config) { { script: 'something' } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns an error message' do
expect(subject.errors.first)
.to match /contains unknown keys: script/
end
end
end
end
require 'fast_spec_helper'
require_dependency 'active_model'
describe EE::Gitlab::Ci::Config::Entry::Trigger do
subject { described_class.new(config) }
context 'when trigger config is a non-empty string' do
let(:config) { 'some/project' }
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'is returns a trigger configuration hash' do
expect(subject.value).to eq(project: 'some/project')
end
end
end
context 'when trigger config an empty string' do
let(:config) { '' }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about an empty config' do
expect(subject.errors.first)
.to match /config can't be blank/
end
end
end
context 'when trigger is a hash' do
context 'when branch is not provided' do
let(:config) { { project: 'some/project' } }
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'is returns a trigger configuration hash' do
expect(subject.value).to eq(project: 'some/project')
end
end
end
context 'when branch is provided' do
let(:config) { { project: 'some/project', branch: 'feature' } }
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'is returns a trigger configuration hash' do
expect(subject.value)
.to eq(project: 'some/project', branch: 'feature')
end
end
end
context 'when config contains unknown keys' do
let(:config) { { project: 'some/project', unknown: 123 } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about unknown config key' do
expect(subject.errors.first)
.to match /config contains unknown keys: unknown/
end
end
end
end
context 'when trigger configuration is not valid' do
context 'when branch is not provided' do
let(:config) { 123 }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns an error message' do
expect(subject.errors.first)
.to match /has to be either a string or a hash/
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Jobs do
subject do
described_class.new(
{
'.hidden_job'.to_sym => { script: 'something' },
regular_job: { script: 'something' },
my_trigger: { trigger: 'my/project' }
}
)
end
context 'when cross-project pipeline triggers are enabled' do
before do
stub_feature_flags(cross_project_pipeline_triggers: true)
subject.compose!
end
describe '#node_type' do
it 'correctly identifies hidden jobs' do
expect(subject.node_type(:'.hidden_job'))
.to eq ::Gitlab::Ci::Config::Entry::Hidden
end
it 'correctly identifies regular jobs' do
expect(subject.node_type(:regular_job))
.to eq ::Gitlab::Ci::Config::Entry::Job
end
it 'correctly identifies cross-project triggers' do
expect(subject.node_type(:my_trigger))
.to eq ::EE::Gitlab::Ci::Config::Entry::Bridge
end
end
describe '#bridge?' do
it 'returns true when a job is a trigger' do
expect(subject.bridge?(:my_trigger)).to be true
end
it 'returns false when a job is not a trigger' do
expect(subject.bridge?(:regular_job)).to be false
end
end
describe '#hidden?' do
it 'does not claim that a bridge job is hidden' do
expect(subject.hidden?(:my_trigger)).to be false
end
end
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'returns a correct hash representing all jobs' do
expect(subject.value).to eq(
my_trigger: {
name: :my_trigger,
trigger: { project: 'my/project' },
stage: 'test',
only: { refs: %w[branches tags] },
ignore: false
},
regular_job: {
script: %w[something],
name: :regular_job,
stage: 'test',
only: { refs: %w[branches tags] },
ignore: false
})
end
end
end
context 'when cross-project pipeline triggers are disabled' do
before do
stub_feature_flags(cross_project_pipeline_triggers: false)
subject.compose!
end
describe '#node_type' do
it 'correctly identifies hidden jobs' do
expect(subject.node_type(:'.hidden_job'))
.to eq ::Gitlab::Ci::Config::Entry::Hidden
end
it 'correctly identifies regular jobs' do
expect(subject.node_type(:regular_job))
.to eq ::Gitlab::Ci::Config::Entry::Job
end
it 'does not identify trigger job as a bridge job' do
expect(subject.node_type(:my_trigger))
.to eq ::Gitlab::Ci::Config::Entry::Job
end
end
describe '#bridge?' do
it 'returns false even when a job is a trigger' do
expect(subject.bridge?(:my_trigger)).to be false
end
it 'returns false when a job is not a trigger' do
expect(subject.bridge?(:regular_job)).to be false
end
end
describe '#valid?' do
it { is_expected.not_to be_valid }
end
end
end
......@@ -46,7 +46,8 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
service.execute(bridge)
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq 'invalid_bridge_trigger'
expect(bridge.failure_reason)
.to eq 'downstream_bridge_project_not_found'
end
end
......@@ -60,7 +61,8 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
service.execute(bridge)
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq 'invalid_bridge_trigger'
expect(bridge.failure_reason)
.to eq 'downstream_bridge_project_not_found'
end
end
......
......@@ -68,6 +68,35 @@ describe Ci::CreatePipelineService, '#execute' do
end
end
describe 'cross-project pipeline triggers' do
before do
stub_feature_flags(cross_project_pipeline_triggers: true)
stub_ci_pipeline_yaml_file <<~YAML
test:
script: rspec
deploy:
stage: deploy
trigger: my/project
YAML
end
it 'creates bridge jobs correctly' do
pipeline = create_pipeline!
test = pipeline.statuses.find_by(name: 'test')
bridge = pipeline.statuses.find_by(name: 'deploy')
expect(pipeline).to be_persisted
expect(test).to be_a Ci::Build
expect(bridge).to be_a Ci::Bridge
expect(bridge.stage).to eq 'deploy'
expect(pipeline.statuses).to match_array [test, bridge]
expect(bridge.options).to eq(trigger: { project: 'my/project' })
end
end
def create_pipeline!
service.execute(:push)
end
......
require 'spec_helper'
describe Ci::ProcessPipelineService, '#execute' do
set(:user) { create(:user) }
set(:project) { create(:project, :repository) }
set(:downstream) { create(:project, :repository) }
set(:pipeline) do
create(:ci_empty_pipeline, ref: 'master', project: project, user: user)
end
before do
project.add_maintainer(user)
downstream.add_developer(user)
end
describe 'cross-project pipelines' do
before do
create_processable(:ci_build, name: 'test', stage: 'test')
create_processable(:ci_bridge, name: 'cross',
stage: 'build',
downstream: downstream)
create_processable(:ci_build, name: 'deploy', stage: 'deploy')
stub_ci_pipeline_to_return_yaml_file
end
it 'creates a downstream cross-project pipeline' do
pipeline.process!
expect_statuses(%w[test pending], %w[cross created], %w[deploy created])
update_build_status(:test, :success)
expect_statuses(%w[test success], %w[cross success], %w[deploy pending])
expect(downstream.ci_pipelines).to be_one
expect(downstream.ci_pipelines.first).to be_pending
end
end
def expect_statuses(*statuses)
statuses.each do |name, status|
pipeline.statuses.find_by(name: name).yield_self do |build|
expect(build.status).to eq status
end
end
end
def update_build_status(name, status)
pipeline.builds.find_by(name: name).public_send(status)
end
def create_processable(type, name:, **opts)
stages = %w[test build deploy]
index = stages.index(opts.fetch(:stage, 'test'))
create(type, status: :created,
name: name,
pipeline: pipeline,
stage_idx: index,
user: user,
**opts)
end
end
......@@ -67,7 +67,7 @@ module Gitlab
entry :only, Entry::Policy,
description: 'Refs policy this job will be executed for.',
default: { refs: %w[branches tags] }
default: Entry::Policy::DEFAULT_ONLY
entry :except, Entry::Policy,
description: 'Refs policy this job will be executed for.'
......
......@@ -28,11 +28,15 @@ module Gitlab
name.to_s.start_with?('.')
end
def node_type(name)
hidden?(name) ? Entry::Hidden : Entry::Job
end
# rubocop: disable CodeReuse/ActiveRecord
def compose!(deps = nil)
super do
@config.each do |name, config|
node = hidden?(name) ? Entry::Hidden : Entry::Job
node = node_type(name)
factory = ::Gitlab::Config::Entry::Factory.new(node)
.value(config || {})
......@@ -54,3 +58,5 @@ module Gitlab
end
end
end
::Gitlab::Ci::Config::Entry::Jobs.prepend(::EE::Gitlab::Ci::Config::Entry::Jobs)
......@@ -11,6 +11,8 @@ module Gitlab
strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) }
strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) }
DEFAULT_ONLY = { refs: %w[branches tags] }.freeze
class RefsPolicy < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
......
......@@ -38,9 +38,17 @@ module Gitlab
)
end
def bridge?
@attributes.to_h.dig(:options, :trigger).present?
end
def to_resource
strong_memoize(:resource) do
::Ci::Build.new(attributes)
if bridge?
::Ci::Bridge.new(attributes)
else
::Ci::Build.new(attributes)
end
end
end
end
......
......@@ -39,7 +39,13 @@ module Gitlab
def to_resource
strong_memoize(:stage) do
::Ci::Stage.new(attributes).tap do |stage|
seeds.each { |seed| stage.builds << seed.to_resource }
seeds.each do |seed|
if seed.bridge?
stage.bridges << seed.to_resource
else
stage.builds << seed.to_resource
end
end
end
end
end
......
......@@ -18,7 +18,6 @@ module Gitlab
end
def details_path
raise NotImplementedError
end
end
end
......
......@@ -33,7 +33,7 @@ module Gitlab
{ stage_idx: @stages.index(job[:stage]),
stage: job[:stage],
tag_list: job[:tags] || [],
tag_list: job[:tags],
name: job[:name].to_s,
allow_failure: job[:ignore],
when: job[:when] || 'on_success',
......@@ -53,8 +53,9 @@ module Gitlab
retry: job[:retry],
parallel: job[:parallel],
instance: job[:instance],
start_in: job[:start_in]
}.compact }
start_in: job[:start_in],
trigger: job[:trigger]
}.compact }.compact
end
def stage_builds_attributes(stage)
......
......@@ -10,8 +10,16 @@ FactoryBot.define do
pipeline factory: :ci_pipeline
transient { downstream nil }
after(:build) do |bridge, evaluator|
bridge.project ||= bridge.pipeline.project
if evaluator.downstream.present?
bridge.options = bridge.options.to_h.merge(
trigger: { project: evaluator.downstream.full_path }
)
end
end
end
end
......@@ -286,6 +286,49 @@ describe 'Pipeline', :js do
end
end
context 'when a bridge job exists' do
include_context 'pipeline builds'
let(:project) { create(:project, :repository) }
let(:downstream) { create(:project, :repository) }
let(:pipeline) do
create(:ci_pipeline, project: project,
ref: 'master',
sha: project.commit.id,
user: user)
end
let!(:bridge) do
create(:ci_bridge, pipeline: pipeline,
name: 'cross-build',
user: user,
downstream: downstream)
end
describe 'GET /:project/pipelines/:id' do
before do
visit project_pipeline_path(project, pipeline)
end
it 'shows the pipeline with a bridge job' do
expect(page).to have_selector('.pipeline-visualization')
expect(page).to have_content('cross-build')
end
end
describe 'GET /:project/pipelines/:id/builds' do
before do
visit builds_project_pipeline_path(project, pipeline)
end
it 'shows a bridge job on a list' do
expect(page).to have_content('cross-build')
expect(page).to have_content(bridge.id)
end
end
end
describe 'GET /:project/pipelines/:id/builds' do
include_context 'pipeline builds'
......
......@@ -163,14 +163,14 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
->(pipeline) { pipeline.variables.create!(key: 'VAR', value: '123') }
end
it 'raises exception' do
it 'wastes pipeline iid' do
expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved)
end
it 'wastes pipeline iid' do
expect { step.perform! }.to raise_error
last_iid = InternalId.ci_pipelines
.where(project_id: project.id)
.last.last_value
expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
expect(last_iid).to be > 0
end
end
end
......
......@@ -5,8 +5,7 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:attributes) do
{ name: 'rspec',
ref: 'master' }
{ name: 'rspec', ref: 'master' }
end
subject do
......@@ -21,10 +20,45 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
describe '#bridge?' do
context 'when job is a bridge' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
it { is_expected.to be_bridge }
end
context 'when trigger definition is empty' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: '' } }
end
it { is_expected.not_to be_bridge }
end
context 'when job is not a bridge' do
it { is_expected.not_to be_bridge }
end
end
describe '#to_resource' do
it 'returns a valid build resource' do
expect(subject.to_resource).to be_a(::Ci::Build)
expect(subject.to_resource).to be_valid
context 'when job is not a bridge' do
it 'returns a valid build resource' do
expect(subject.to_resource).to be_a(::Ci::Build)
expect(subject.to_resource).to be_valid
end
end
context 'when job is a bridge' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
it 'returns a valid bridge resource' do
expect(subject.to_resource).to be_a(::Ci::Bridge)
expect(subject.to_resource).to be_valid
end
end
it 'memoizes a resource object' do
......
......@@ -62,8 +62,18 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
expect(subject.seeds.map(&:attributes)).to all(include(ref: 'master'))
expect(subject.seeds.map(&:attributes)).to all(include(tag: false))
expect(subject.seeds.map(&:attributes)).to all(include(project: pipeline.project))
expect(subject.seeds.map(&:attributes))
.to all(include(trigger_request: pipeline.trigger_requests.first))
end
context 'when a legacy trigger exists' do
before do
create(:ci_trigger_request, pipeline: pipeline)
end
it 'returns build seeds including legacy trigger' do
expect(pipeline.legacy_trigger).not_to be_nil
expect(subject.seeds.map(&:attributes))
.to all(include(trigger_request: pipeline.legacy_trigger))
end
end
context 'when a ref is protected' do
......
......@@ -21,15 +21,12 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"]
},
allow_failure: false,
when: "on_success",
environment: nil,
yaml_variables: []
})
end
......@@ -154,12 +151,9 @@ module Gitlab
builds:
[{ stage_idx: 1,
stage: "test",
tag_list: [],
name: "rspec",
allow_failure: false,
when: "on_success",
environment: nil,
coverage_regex: nil,
yaml_variables: [],
options: { script: ["rspec"] },
only: { refs: ["branches"] },
......@@ -169,12 +163,9 @@ module Gitlab
builds:
[{ stage_idx: 2,
stage: "deploy",
tag_list: [],
name: "prod",
allow_failure: false,
when: "on_success",
environment: nil,
coverage_regex: nil,
yaml_variables: [],
options: { script: ["cap prod"] },
only: { refs: ["tags"] },
......@@ -344,8 +335,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
......@@ -356,7 +345,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
environment: nil,
yaml_variables: []
})
end
......@@ -378,8 +366,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
......@@ -390,7 +376,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
environment: nil,
yaml_variables: []
})
end
......@@ -410,8 +395,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
......@@ -420,7 +403,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
environment: nil,
yaml_variables: []
})
end
......@@ -438,8 +420,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
......@@ -448,7 +428,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
environment: nil,
yaml_variables: []
})
end
......@@ -763,8 +742,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
......@@ -779,7 +756,6 @@ module Gitlab
},
when: "on_success",
allow_failure: false,
environment: nil,
yaml_variables: []
})
end
......@@ -976,14 +952,11 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "normal_job",
coverage_regex: nil,
tag_list: [],
options: {
script: ["test"]
},
when: "on_success",
allow_failure: false,
environment: nil,
yaml_variables: []
})
end
......@@ -1023,28 +996,22 @@ module Gitlab
stage: "build",
stage_idx: 0,
name: "job1",
coverage_regex: nil,
tag_list: [],
options: {
script: ["execute-script-for-job"]
},
when: "on_success",
allow_failure: false,
environment: nil,
yaml_variables: []
})
expect(subject.second).to eq({
stage: "build",
stage_idx: 0,
name: "job2",
coverage_regex: nil,
tag_list: [],
options: {
script: ["execute-script-for-job"]
},
when: "on_success",
allow_failure: false,
environment: nil,
yaml_variables: []
})
end
......
......@@ -114,6 +114,7 @@ ci_pipelines:
- stages
- statuses
- builds
- processables
- trigger_requests
- variables
- auto_canceled_by
......@@ -137,6 +138,7 @@ stages:
- pipeline
- statuses
- builds
- bridges
statuses:
- project
- pipeline
......
......@@ -43,6 +43,29 @@ describe Ci::Pipeline, :mailer do
end
end
describe '.processables' do
before do
create(:ci_build, name: 'build', pipeline: pipeline)
create(:ci_bridge, name: 'bridge', pipeline: pipeline)
create(:commit_status, name: 'commit status', pipeline: pipeline)
create(:generic_commit_status, name: 'generic status', pipeline: pipeline)
end
it 'has an association with processable CI/CD entities' do
pipeline.processables.pluck('name').yield_self do |processables|
expect(processables).to match_array %w[build bridge]
end
end
it 'makes it possible to append a new processable' do
pipeline.processables << build(:ci_bridge)
pipeline.save!
expect(pipeline.processables.reload.count).to eq 3
end
end
describe '.sort_by_merge_request_pipelines' do
subject { described_class.sort_by_merge_request_pipelines }
......
require_relative '../../../ee/spec/support/helpers/ee/stub_object_storage'
module StubObjectStorage
prepend EE::StubObjectStorage
......
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