Commit 95889f83 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'revert-3cb41e08' into 'master'

Revert "Merge branch 'create-downstream-pipeline-in-same-project' into 'master'"

See merge request gitlab-org/gitlab!22571
parents 16f76007 0445278a
...@@ -17,7 +17,7 @@ class PipelinesFinder ...@@ -17,7 +17,7 @@ class PipelinesFinder
return Ci::Pipeline.none return Ci::Pipeline.none
end end
items = pipelines.no_child items = pipelines
items = by_scope(items) items = by_scope(items)
items = by_status(items) items = by_status(items)
items = by_ref(items) items = by_ref(items)
......
...@@ -54,10 +54,6 @@ module Ci ...@@ -54,10 +54,6 @@ module Ci
def to_partial_path def to_partial_path
'projects/generic_commit_statuses/generic_commit_status' 'projects/generic_commit_statuses/generic_commit_status'
end end
def yaml_for_downstream
nil
end
end end
end end
......
...@@ -61,9 +61,7 @@ module Ci ...@@ -61,9 +61,7 @@ module Ci
has_one :chat_data, class_name: 'Ci::PipelineChatData' has_one :chat_data, class_name: 'Ci::PipelineChatData'
has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline
has_many :child_pipelines, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :sourced_pipelines, source: :pipeline
has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline
has_one :parent_pipeline, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :source_pipeline, source: :source_pipeline
has_one :source_job, through: :source_pipeline, source: :source_job has_one :source_job, through: :source_pipeline, source: :source_job
has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline
...@@ -215,7 +213,6 @@ module Ci ...@@ -215,7 +213,6 @@ module Ci
end end
scope :internal, -> { where(source: internal_sources) } scope :internal, -> { where(source: internal_sources) }
scope :no_child, -> { where.not(source: :parent_pipeline) }
scope :ci_sources, -> { where(config_source: ::Ci::PipelineEnums.ci_config_sources_values) } scope :ci_sources, -> { where(config_source: ::Ci::PipelineEnums.ci_config_sources_values) }
scope :for_user, -> (user) { where(user: user) } scope :for_user, -> (user) { where(user: user) }
scope :for_sha, -> (sha) { where(sha: sha) } scope :for_sha, -> (sha) { where(sha: sha) }
...@@ -511,6 +508,10 @@ module Ci ...@@ -511,6 +508,10 @@ module Ci
builds.skipped.after_stage(stage_idx).find_each(&:process) builds.skipped.after_stage(stage_idx).find_each(&:process)
end end
def child?
false
end
def latest? def latest?
return false unless git_ref && commit.present? return false unless git_ref && commit.present?
...@@ -693,24 +694,6 @@ module Ci ...@@ -693,24 +694,6 @@ module Ci
all_merge_requests.order(id: :desc) all_merge_requests.order(id: :desc)
end end
# If pipeline is a child of another pipeline, include the parent
# and the siblings, otherwise return only itself.
def same_family_pipeline_ids
if (parent = parent_pipeline)
[parent.id] + parent.child_pipelines.pluck(:id)
else
[self.id]
end
end
def child?
parent_pipeline.present?
end
def parent?
child_pipelines.exists?
end
def detailed_status(current_user) def detailed_status(current_user)
Gitlab::Ci::Status::Pipeline::Factory Gitlab::Ci::Status::Pipeline::Factory
.new(self, current_user) .new(self, current_user)
......
...@@ -23,11 +23,10 @@ module Ci ...@@ -23,11 +23,10 @@ module Ci
schedule: 4, schedule: 4,
api: 5, api: 5,
external: 6, external: 6,
cross_project_pipeline: 7, pipeline: 7,
chat: 8, chat: 8,
merge_request_event: 10, merge_request_event: 10,
external_pull_request_event: 11, external_pull_request_event: 11
parent_pipeline: 12
} }
end end
...@@ -39,8 +38,7 @@ module Ci ...@@ -39,8 +38,7 @@ module Ci
repository_source: 1, repository_source: 1,
auto_devops_source: 2, auto_devops_source: 2,
remote_source: 4, remote_source: 4,
external_project_source: 5, external_project_source: 5
bridge_source: 6
} }
end end
......
...@@ -18,8 +18,6 @@ module Ci ...@@ -18,8 +18,6 @@ module Ci
validates :source_project, presence: true validates :source_project, presence: true
validates :source_job, presence: true validates :source_job, presence: true
validates :source_pipeline, presence: true validates :source_pipeline, presence: true
scope :same_project, -> { where(arel_table[:source_project_id].eq(arel_table[:project_id])) }
end end
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class PipelineDetailsEntity < PipelineEntity class PipelineDetailsEntity < PipelineEntity
expose :project, using: ProjectEntity
expose :flags do expose :flags do
expose :latest?, as: :latest expose :latest?, as: :latest
end end
......
...@@ -41,7 +41,6 @@ class PipelineSerializer < BaseSerializer ...@@ -41,7 +41,6 @@ class PipelineSerializer < BaseSerializer
def preloaded_relations def preloaded_relations
[ [
:latest_statuses_ordered_by_stage, :latest_statuses_ordered_by_stage,
:project,
:stages, :stages,
{ {
failed_builds: %i(project metadata) failed_builds: %i(project metadata)
......
...@@ -23,7 +23,7 @@ module Ci ...@@ -23,7 +23,7 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze
# rubocop: disable Metrics/ParameterLists # rubocop: disable Metrics/ParameterLists
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, bridge: nil, **options, &block) def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, **options, &block)
@pipeline = Ci::Pipeline.new @pipeline = Ci::Pipeline.new
command = Gitlab::Ci::Pipeline::Chain::Command.new( command = Gitlab::Ci::Pipeline::Chain::Command.new(
...@@ -46,7 +46,6 @@ module Ci ...@@ -46,7 +46,6 @@ module Ci
current_user: current_user, current_user: current_user,
push_options: params[:push_options] || {}, push_options: params[:push_options] || {},
chat_data: params[:chat_data], chat_data: params[:chat_data],
bridge: bridge,
**extra_options(options)) **extra_options(options))
sequence = Gitlab::Ci::Pipeline::Chain::Sequence sequence = Gitlab::Ci::Pipeline::Chain::Sequence
...@@ -105,14 +104,14 @@ module Ci ...@@ -105,14 +104,14 @@ module Ci
if Feature.enabled?(:ci_support_interruptible_pipelines, project, default_enabled: true) if Feature.enabled?(:ci_support_interruptible_pipelines, project, default_enabled: true)
project.ci_pipelines project.ci_pipelines
.where(ref: pipeline.ref) .where(ref: pipeline.ref)
.where.not(id: pipeline.same_family_pipeline_ids) .where.not(id: pipeline.id)
.where.not(sha: project.commit(pipeline.ref).try(:id)) .where.not(sha: project.commit(pipeline.ref).try(:id))
.alive_or_scheduled .alive_or_scheduled
.with_only_interruptible_builds .with_only_interruptible_builds
else else
project.ci_pipelines project.ci_pipelines
.where(ref: pipeline.ref) .where(ref: pipeline.ref)
.where.not(id: pipeline.same_family_pipeline_ids) .where.not(id: pipeline.id)
.where.not(sha: project.commit(pipeline.ref).try(:id)) .where.not(sha: project.commit(pipeline.ref).try(:id))
.created_or_pending .created_or_pending
end end
......
...@@ -44,7 +44,7 @@ module Ci ...@@ -44,7 +44,7 @@ module Ci
return error("400 Job has to be running", 400) unless job.running? return error("400 Job has to be running", 400) unless job.running?
pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref]) pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref])
.execute(:cross_project_pipeline, ignore_skip_ci: true) do |pipeline| .execute(:pipeline, ignore_skip_ci: true) do |pipeline|
source = job.sourced_pipelines.build( source = job.sourced_pipelines.build(
source_pipeline: job.pipeline, source_pipeline: job.pipeline,
source_project: job.project, source_project: job.project,
......
---
title: Allow an upstream pipeline to create a downstream pipeline in the same project
merge_request: 20930
author:
type: added
...@@ -4,7 +4,6 @@ module EE ...@@ -4,7 +4,6 @@ module EE
module Ci module Ci
module Bridge module Bridge
extend ActiveSupport::Concern extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
InvalidBridgeTypeError = Class.new(StandardError) InvalidBridgeTypeError = Class.new(StandardError)
...@@ -99,42 +98,19 @@ module EE ...@@ -99,42 +98,19 @@ module EE
self.user self.user
end end
def target_project def target_project_path
downstream_project || upstream_project downstream_project || upstream_project
end end
def triggers_child_pipeline?
yaml_for_downstream.present?
end
override :yaml_for_downstream
def yaml_for_downstream
strong_memoize(:yaml_for_downstream) do
includes = options&.dig(:trigger, :include)
YAML.dump('include' => includes) if includes
end
end
def downstream_pipeline_params
return child_params if triggers_child_pipeline?
return cross_project_params if downstream_project.present?
{}
end
def downstream_project def downstream_project
strong_memoize(:downstream_project) do strong_memoize(:downstream_project) do
if downstream_project_path options&.dig(:trigger, :project)
::Project.find_by_full_path(downstream_project_path)
elsif triggers_child_pipeline?
project
end
end end
end end
def upstream_project def upstream_project
strong_memoize(:upstream_project) do strong_memoize(:upstream_project) do
upstream_project_path && ::Project.find_by_full_path(upstream_project_path) options&.dig(:bridge_needs, :pipeline)
end end
end end
...@@ -162,51 +138,6 @@ module EE ...@@ -162,51 +138,6 @@ module EE
end end
end end
end end
def downstream_project_path
strong_memoize(:downstream_project_path) do
options&.dig(:trigger, :project)
end
end
def upstream_project_path
strong_memoize(:upstream_project_path) do
options&.dig(:bridge_needs, :pipeline)
end
end
private
def cross_project_params
{
project: downstream_project,
source: :cross_project_pipeline,
target_revision: {
ref: target_ref || downstream_project.default_branch
},
execute_params: { ignore_skip_ci: true }
}
end
def child_params
parent_pipeline = pipeline
{
project: project,
source: :parent_pipeline,
target_revision: {
ref: parent_pipeline.ref,
checkout_sha: parent_pipeline.sha,
before: parent_pipeline.before_sha,
source_sha: parent_pipeline.source_sha,
target_sha: parent_pipeline.target_sha
},
execute_params: {
ignore_skip_ci: true,
bridge: self
}
}
end
end end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
module Ci module Ci
# TODO: rename this (and worker) to CreateDownstreamPipelineService
class CreateCrossProjectPipelineService < ::BaseService class CreateCrossProjectPipelineService < ::BaseService
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
def execute(bridge) def execute(bridge)
@bridge = bridge @bridge = bridge
pipeline_params = @bridge.downstream_pipeline_params unless target_project_exists?
target_ref = pipeline_params.dig(:target_revision, :ref) return bridge.drop!(:downstream_bridge_project_not_found)
end
return unless ensure_preconditions!(target_ref)
service = ::Ci::CreatePipelineService.new( if target_project == project
pipeline_params.fetch(:project), return bridge.drop!(:invalid_bridge_trigger)
current_user, end
pipeline_params.fetch(:target_revision))
service.execute( unless can_create_cross_pipeline?
pipeline_params.fetch(:source), pipeline_params[:execute_params]) do |pipeline| return bridge.drop!(:insufficient_bridge_permissions)
@bridge.sourced_pipelines.build( end
source_pipeline: @bridge.pipeline,
source_project: @bridge.project,
project: @bridge.downstream_project,
pipeline: pipeline)
pipeline.variables.build(@bridge.downstream_variables) create_pipeline!
end
end end
private private
def ensure_preconditions!(target_ref) def target_project_exists?
unless downstream_project_accessible? target_project.present? &&
@bridge.drop!(:downstream_bridge_project_not_found) can?(current_user, :read_project, target_project)
return false end
end
# TODO: Remove this condition if favour of model validation
# https://gitlab.com/gitlab-org/gitlab/issues/38338
if downstream_project == project && !@bridge.triggers_child_pipeline?
@bridge.drop!(:invalid_bridge_trigger)
return false
end
unless can_create_downstream_pipeline?(target_ref) def can_create_cross_pipeline?
@bridge.drop!(:insufficient_bridge_permissions) can?(current_user, :update_pipeline, project) &&
return false can?(target_user, :create_pipeline, target_project) &&
end can_update_branch?
end
true def can_update_branch?
::Gitlab::UserAccess.new(target_user, project: target_project).can_update_branch?(target_ref)
end end
def downstream_project_accessible? def create_pipeline!
downstream_project.present? && ::Ci::CreatePipelineService
can?(current_user, :read_project, downstream_project) .new(target_project, target_user, ref: target_ref)
.execute(:pipeline, ignore_skip_ci: true) do |pipeline|
@bridge.sourced_pipelines.build(
source_pipeline: @bridge.pipeline,
source_project: @bridge.project,
project: target_project,
pipeline: pipeline)
pipeline.variables.build(@bridge.downstream_variables)
end
end end
def can_create_downstream_pipeline?(target_ref) def target_user
can?(current_user, :update_pipeline, project) && strong_memoize(:target_user) { @bridge.target_user }
can?(current_user, :create_pipeline, downstream_project) &&
can_update_branch?(target_ref)
end end
def can_update_branch?(target_ref) def target_ref
::Gitlab::UserAccess.new(current_user, project: downstream_project).can_update_branch?(target_ref) strong_memoize(:target_ref) do
@bridge.target_ref || target_project.default_branch
end
end end
def downstream_project def target_project
strong_memoize(:downstream_project) do strong_memoize(:target_project) do
@bridge.downstream_project Project.find_by_full_path(@bridge.target_project_path)
end end
end end
end end
......
...@@ -5,7 +5,7 @@ module Ci ...@@ -5,7 +5,7 @@ module Ci
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
def execute(bridge) def execute(bridge)
return unless bridge.upstream_project_path return unless bridge.upstream_project
@bridge = bridge @bridge = bridge
...@@ -29,7 +29,7 @@ module Ci ...@@ -29,7 +29,7 @@ module Ci
def upstream_project def upstream_project
strong_memoize(:upstream_project) do strong_memoize(:upstream_project) do
@bridge.upstream_project ::Project.find_by_full_path(@bridge.target_project_path)
end end
end end
......
...@@ -22,61 +22,19 @@ module EE ...@@ -22,61 +22,19 @@ module EE
end end
end end
class ComplexTrigger < ::Gitlab::Config::Entry::Simplifiable class ComplexTrigger < ::Gitlab::Config::Entry::Node
strategy :CrossProjectTrigger, if: -> (config) { !config.key?(:include) } include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
strategy :SameProjectTrigger, if: -> (config) do
::Feature.enabled?(:ci_parent_child_pipeline) &&
config.key?(:include)
end
class CrossProjectTrigger < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[project branch strategy].freeze
attributes :project, :branch, :strategy
validations do
validates :config, presence: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :project, presence: true
validates :branch, type: String, allow_nil: true
validates :strategy, type: String, inclusion: { in: %w[depend], message: 'should be depend' }, allow_nil: true
end
end
class SameProjectTrigger < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
include ::Gitlab::Config::Entry::Configurable
ALLOWED_KEYS = %i[strategy include].freeze
attributes :strategy
validations do
validates :config, presence: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :strategy, type: String, inclusion: { in: %w[depend], message: 'should be depend' }, allow_nil: true
end
entry :include, ::Gitlab::Ci::Config::Entry::Includes,
description: 'List of external YAML files to include.',
reserved: true
def value ALLOWED_KEYS = %i[project branch strategy].freeze
@config attributes :project, :branch, :strategy
end
end
class UnknownStrategy < ::Gitlab::Config::Entry::Node validations do
def errors validates :config, presence: true
if ::Feature.enabled?(:ci_parent_child_pipeline) validates :config, allowed_keys: ALLOWED_KEYS
['config must specify either project or include'] validates :project, presence: true
else validates :branch, type: String, allow_nil: true
['config must specify project'] validates :strategy, type: String, inclusion: { in: %w[depend], message: 'should be depend' }, allow_nil: true
end
end
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'fast_spec_helper'
require_dependency 'active_model'
describe EE::Gitlab::Ci::Config::Entry::Trigger do describe EE::Gitlab::Ci::Config::Entry::Trigger do
subject { described_class.new(config) } subject { described_class.new(config) }
...@@ -82,53 +83,6 @@ describe EE::Gitlab::Ci::Config::Entry::Trigger do ...@@ -82,53 +83,6 @@ describe EE::Gitlab::Ci::Config::Entry::Trigger do
end end
end end
context '#include' do
context 'with simple include' do
let(:config) { { include: 'path/to/config.yml' } }
it { is_expected.to be_valid }
it 'returns a trigger configuration hash' do
expect(subject.value).to eq(include: 'path/to/config.yml' )
end
end
context 'with project' do
let(:config) { { project: 'some/project', include: 'path/to/config.yml' } }
it { is_expected.not_to be_valid }
it 'is returns an error' do
expect(subject.errors.first)
.to match /config contains unknown keys: project/
end
end
context 'with branch' do
let(:config) { { branch: 'feature', include: 'path/to/config.yml' } }
it { is_expected.not_to be_valid }
it 'is returns an error' do
expect(subject.errors.first)
.to match /config contains unknown keys: branch/
end
end
context 'when feature flag is off' do
before do
stub_feature_flags(ci_parent_child_pipeline: false)
end
let(:config) { { include: 'path/to/config.yml' } }
it 'is returns an error if include is used' do
expect(subject.errors.first)
.to match /config must specify project/
end
end
end
context 'when config contains unknown keys' do context 'when config contains unknown keys' do
let(:config) { { project: 'some/project', unknown: 123 } } let(:config) { { project: 'some/project', unknown: 123 } }
......
...@@ -4,7 +4,6 @@ require 'spec_helper' ...@@ -4,7 +4,6 @@ require 'spec_helper'
describe Ci::Bridge do describe Ci::Bridge do
set(:project) { create(:project) } set(:project) { create(:project) }
set(:target_project) { create(:project, name: 'project', namespace: create(:namespace, name: 'my')) }
set(:pipeline) { create(:ci_pipeline, project: project) } set(:pipeline) { create(:ci_pipeline, project: project) }
let(:bridge) do let(:bridge) do
...@@ -158,10 +157,10 @@ describe Ci::Bridge do ...@@ -158,10 +157,10 @@ describe Ci::Bridge do
end end
end end
describe '#target_project' do describe '#target_project_path' do
context 'when trigger is defined' do context 'when trigger is defined' do
it 'returns a full path of a project' do it 'returns a full path of a project' do
expect(bridge.target_project).to eq target_project expect(bridge.target_project_path).to eq 'my/project'
end end
end end
...@@ -169,7 +168,7 @@ describe Ci::Bridge do ...@@ -169,7 +168,7 @@ describe Ci::Bridge do
let(:options) { { trigger: {} } } let(:options) { { trigger: {} } }
it 'returns nil' do it 'returns nil' do
expect(bridge.target_project).to be_nil expect(bridge.target_project_path).to be_nil
end end
end end
end end
...@@ -290,61 +289,4 @@ describe Ci::Bridge do ...@@ -290,61 +289,4 @@ describe Ci::Bridge do
expect(bridge.metadata.config_options).to be bridge.options expect(bridge.metadata.config_options).to be bridge.options
end end
end end
describe '#triggers_child_pipeline?' do
subject { bridge.triggers_child_pipeline? }
context 'when bridge defines a downstream YAML' do
let(:options) do
{
trigger: {
include: 'path/to/child.yml'
}
}
end
it { is_expected.to be_truthy }
end
context 'when bridge does not define a downstream YAML' do
let(:options) do
{
trigger: {
project: project.full_path
}
}
end
it { is_expected.to be_falsey }
end
end
describe '#yaml_for_downstream' do
subject { bridge.yaml_for_downstream }
context 'when bridge defines a downstream YAML' do
let(:options) do
{
trigger: {
include: 'path/to/child.yml'
}
}
end
let(:yaml) do
<<~EOY
---
include: path/to/child.yml
EOY
end
it { is_expected.to eq yaml }
end
context 'when bridge does not define a downstream YAML' do
let(:options) { {} }
it { is_expected.to be_nil }
end
end
end end
...@@ -60,7 +60,7 @@ describe API::Triggers do ...@@ -60,7 +60,7 @@ describe API::Triggers do
expect { subject }.to change(Ci::Pipeline, :count) expect { subject }.to change(Ci::Pipeline, :count)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(Ci::Pipeline.last.source).to eq('cross_project_pipeline') expect(Ci::Pipeline.last.source).to eq('pipeline')
expect(Ci::Pipeline.last.triggered_by_pipeline).not_to be_nil expect(Ci::Pipeline.last.triggered_by_pipeline).not_to be_nil
expect(Ci::Sources::Pipeline.last).to have_attributes( expect(Ci::Sources::Pipeline.last).to have_attributes(
pipeline_id: (a_value > 0), pipeline_id: (a_value > 0),
...@@ -94,7 +94,7 @@ describe API::Triggers do ...@@ -94,7 +94,7 @@ describe API::Triggers do
.and change(Ci::PipelineVariable, :count) .and change(Ci::PipelineVariable, :count)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(Ci::Pipeline.last.source).to eq('cross_project_pipeline') expect(Ci::Pipeline.last.source).to eq('pipeline')
expect(Ci::Pipeline.last.triggered_by_pipeline).not_to be_nil expect(Ci::Pipeline.last.triggered_by_pipeline).not_to be_nil
expect(Ci::Pipeline.last.variables.map { |v| { v.key => v.value } }.last).to eq(params[:variables]) expect(Ci::Pipeline.last.variables.map { |v| { v.key => v.value } }.last).to eq(params[:variables])
end end
......
...@@ -4,10 +4,10 @@ require 'spec_helper' ...@@ -4,10 +4,10 @@ require 'spec_helper'
describe Ci::CreateCrossProjectPipelineService, '#execute' do describe Ci::CreateCrossProjectPipelineService, '#execute' do
set(:user) { create(:user) } set(:user) { create(:user) }
let(:upstream_project) { create(:project, :repository) } set(:upstream_project) { create(:project, :repository) }
set(:downstream_project) { create(:project, :repository) } set(:downstream_project) { create(:project, :repository) }
let!(:upstream_pipeline) do set(:upstream_pipeline) do
create(:ci_pipeline, :running, project: upstream_project) create(:ci_pipeline, :running, project: upstream_project)
end end
...@@ -30,6 +30,7 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -30,6 +30,7 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
let(:service) { described_class.new(upstream_project, user) } let(:service) { described_class.new(upstream_project, user) }
before do before do
stub_ci_pipeline_to_return_yaml_file
upstream_project.add_developer(user) upstream_project.add_developer(user)
end end
...@@ -86,11 +87,8 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -86,11 +87,8 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
end end
context 'when user can create pipeline in a downstream project' do context 'when user can create pipeline in a downstream project' do
let(:stub_config) { true }
before do before do
downstream_project.add_developer(user) downstream_project.add_developer(user)
stub_ci_pipeline_yaml_file(YAML.dump(rspec: { script: 'rspec' })) if stub_config
end end
it 'creates only one new pipeline' do it 'creates only one new pipeline' do
...@@ -128,95 +126,21 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -128,95 +126,21 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
end end
end end
context 'when downstream project is the same as the job project' do context 'when circular dependency is defined' do
let(:trigger) do let(:trigger) do
{ trigger: { project: upstream_project.full_path } } { trigger: { project: upstream_project.full_path } }
end end
context 'detects a circular dependency' do it 'does not create a new pipeline' do
it 'does not create a new pipeline' do expect { service.execute(bridge) }
expect { service.execute(bridge) } .not_to change { Ci::Pipeline.count }
.not_to change { Ci::Pipeline.count }
end
it 'changes status of the bridge build' do
service.execute(bridge)
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq 'invalid_bridge_trigger'
end
end end
context 'when "include" is provided' do it 'changes status of the bridge build' do
shared_examples 'creates a child pipeline' do service.execute(bridge)
it 'creates only one new pipeline' do
expect { service.execute(bridge) }
.to change { Ci::Pipeline.count }.by(1)
end
it 'creates a child pipeline in the same project' do
pipeline = service.execute(bridge)
pipeline.reload
expect(pipeline.builds.map(&:name)).to eq %w[rspec echo]
expect(pipeline.user).to eq bridge.user
expect(pipeline.project).to eq bridge.project
expect(bridge.sourced_pipelines.first.pipeline).to eq pipeline
expect(pipeline.triggered_by_pipeline).to eq upstream_pipeline
expect(pipeline.source_bridge).to eq bridge
expect(pipeline.source_bridge).to be_a ::Ci::Bridge
end
it 'updates bridge status when downstream pipeline gets proceesed' do
pipeline = service.execute(bridge)
expect(pipeline.reload).to be_pending
expect(bridge.reload).to be_success
end
it 'propagates parent pipeline settings to the child pipeline' do
pipeline = service.execute(bridge)
pipeline.reload
expect(pipeline.ref).to eq(upstream_pipeline.ref)
expect(pipeline.sha).to eq(upstream_pipeline.sha)
expect(pipeline.source_sha).to eq(upstream_pipeline.source_sha)
expect(pipeline.target_sha).to eq(upstream_pipeline.target_sha)
expect(pipeline.target_sha).to eq(upstream_pipeline.target_sha)
expect(pipeline.trigger_requests.last).to eq(bridge.trigger_request)
end
end
before do
file_content = YAML.dump(
rspec: { script: 'rspec' },
echo: { script: 'echo' })
upstream_project.repository.create_file(
user, 'child-pipeline.yml', file_content, message: 'message', branch_name: 'master')
upstream_pipeline.update!(sha: upstream_project.commit.id)
end
let(:stub_config) { false }
let(:trigger) do
{
trigger: { include: 'child-pipeline.yml' }
}
end
it_behaves_like 'creates a child pipeline'
context 'when latest sha for the ref changed in the meantime' do
before do
upstream_project.repository.create_file(
user, 'another-change', 'test', message: 'message', branch_name: 'master')
end
# it does not auto-cancel pipelines from the same family expect(bridge.reload).to be_failed
it_behaves_like 'creates a child pipeline' expect(bridge.failure_reason).to eq 'invalid_bridge_trigger'
end
end end
end end
......
...@@ -5,7 +5,6 @@ require 'spec_helper' ...@@ -5,7 +5,6 @@ require 'spec_helper'
describe Ci::CreatePipelineService do describe Ci::CreatePipelineService do
subject(:execute) { service.execute(:push) } subject(:execute) { service.execute(:push) }
set(:downstream_project) { create(:project, name: 'project', namespace: create(:namespace, name: 'some'))}
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { create(:admin) } let(:user) { create(:admin) }
let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) } let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) }
...@@ -47,6 +46,6 @@ describe Ci::CreatePipelineService do ...@@ -47,6 +46,6 @@ describe Ci::CreatePipelineService do
it 'persists bridge target project' do it 'persists bridge target project' do
bridge = execute.stages.last.bridges.first bridge = execute.stages.last.bridges.first
expect(bridge.downstream_project).to eq downstream_project expect(bridge.downstream_project).to eq('some/project')
end end
end end
...@@ -73,6 +73,8 @@ describe Ci::CreatePipelineService, '#execute' do ...@@ -73,6 +73,8 @@ describe Ci::CreatePipelineService, '#execute' do
describe 'cross-project pipeline triggers' do describe 'cross-project pipeline triggers' do
before do before do
stub_feature_flags(cross_project_pipeline_triggers: true)
stub_ci_pipeline_yaml_file <<~YAML stub_ci_pipeline_yaml_file <<~YAML
test: test:
script: rspec script: rspec
...@@ -142,41 +144,6 @@ describe Ci::CreatePipelineService, '#execute' do ...@@ -142,41 +144,6 @@ describe Ci::CreatePipelineService, '#execute' do
end end
end end
describe 'child pipeline triggers' do
before do
stub_ci_pipeline_yaml_file <<~YAML
test:
script: rspec
deploy:
variables:
CROSS: downstream
stage: deploy
trigger:
include:
- local: path/to/child.yml
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' => { 'include' => [{ 'local' => 'path/to/child.yml' }] }
)
expect(bridge.yaml_variables)
.to include(key: 'CROSS', value: 'downstream', public: true)
end
end
def create_pipeline! def create_pipeline!
service.execute(:push) service.execute(:push)
end end
......
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
:trigger_request, :schedule, :merge_request, :external_pull_request, :trigger_request, :schedule, :merge_request, :external_pull_request,
:ignore_skip_ci, :save_incompleted, :ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes, :push_options, :seeds_block, :variables_attributes, :push_options,
:chat_data, :allow_mirror_update, :bridge, :chat_data, :allow_mirror_update,
# These attributes are set by Chains during processing: # These attributes are set by Chains during processing:
:config_content, :config_processor, :stage_seeds :config_content, :config_processor, :stage_seeds
) do ) do
......
...@@ -9,7 +9,7 @@ module Gitlab ...@@ -9,7 +9,7 @@ module Gitlab
include Chain::Helpers include Chain::Helpers
SOURCES = [ SOURCES = [
Gitlab::Ci::Pipeline::Chain::Config::Content::Bridge, Gitlab::Ci::Pipeline::Chain::Config::Content::Runtime,
Gitlab::Ci::Pipeline::Chain::Config::Content::Repository, Gitlab::Ci::Pipeline::Chain::Config::Content::Repository,
Gitlab::Ci::Pipeline::Chain::Config::Content::ExternalProject, Gitlab::Ci::Pipeline::Chain::Config::Content::ExternalProject,
Gitlab::Ci::Pipeline::Chain::Config::Content::Remote, Gitlab::Ci::Pipeline::Chain::Config::Content::Remote,
...@@ -17,7 +17,7 @@ module Gitlab ...@@ -17,7 +17,7 @@ module Gitlab
].freeze ].freeze
LEGACY_SOURCES = [ LEGACY_SOURCES = [
Gitlab::Ci::Pipeline::Chain::Config::Content::Bridge, Gitlab::Ci::Pipeline::Chain::Config::Content::Runtime,
Gitlab::Ci::Pipeline::Chain::Config::Content::LegacyRepository, Gitlab::Ci::Pipeline::Chain::Config::Content::LegacyRepository,
Gitlab::Ci::Pipeline::Chain::Config::Content::LegacyAutoDevops Gitlab::Ci::Pipeline::Chain::Config::Content::LegacyAutoDevops
].freeze ].freeze
......
...@@ -6,15 +6,20 @@ module Gitlab ...@@ -6,15 +6,20 @@ module Gitlab
module Chain module Chain
module Config module Config
class Content class Content
class Bridge < Source class Runtime < Source
def content def content
return unless @command.bridge @command.config_content
@command.bridge.yaml_for_downstream
end end
def source def source
:bridge_source # The only case when this source is used is when the config content
# is passed in as parameter to Ci::CreatePipelineService.
# This would only occur with parent/child pipelines which is being
# implemented.
# TODO: change source to return :runtime_source
# https://gitlab.com/gitlab-org/gitlab/merge_requests/21041
nil
end end
end end
end end
......
...@@ -64,19 +64,6 @@ describe PipelinesFinder do ...@@ -64,19 +64,6 @@ describe PipelinesFinder do
end end
end end
context 'when project has child pipelines' do
let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
let!(:child_pipeline) { create(:ci_pipeline, project: project, source: :parent_pipeline) }
let!(:pipeline_source) do
create(:ci_sources_pipeline, pipeline: child_pipeline, source_pipeline: parent_pipeline)
end
it 'filters out child pipelines and show only the parents' do
is_expected.to eq([parent_pipeline])
end
end
HasStatus::AVAILABLE_STATUSES.each do |target| HasStatus::AVAILABLE_STATUSES.each do |target|
context "when status is #{target}" do context "when status is #{target}" do
let(:params) { { status: target } } let(:params) { { status: target } }
......
...@@ -15,42 +15,6 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do ...@@ -15,42 +15,6 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
stub_feature_flags(ci_root_config_content: false) stub_feature_flags(ci_root_config_content: false)
end end
context 'when bridge job is passed in as parameter' do
let(:ci_config_path) { nil }
let(:bridge) { create(:ci_bridge) }
before do
command.bridge = bridge
end
context 'when bridge job has downstream yaml' do
before do
allow(bridge).to receive(:yaml_for_downstream).and_return('the-yaml')
end
it 'returns the content already available in command' do
subject.perform!
expect(pipeline.config_source).to eq 'bridge_source'
expect(command.config_content).to eq 'the-yaml'
end
end
context 'when bridge job does not have downstream yaml' do
before do
allow(bridge).to receive(:yaml_for_downstream).and_return(nil)
end
it 'returns the next available source' do
subject.perform!
expect(pipeline.config_source).to eq 'auto_devops_source'
template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
end
end
context 'when config is defined in a custom path in the repository' do context 'when config is defined in a custom path in the repository' do
let(:ci_config_path) { 'path/to/config.yml' } let(:ci_config_path) { 'path/to/config.yml' }
...@@ -171,23 +135,6 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do ...@@ -171,23 +135,6 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
end end
end end
context 'when bridge job is passed in as parameter' do
let(:ci_config_path) { nil }
let(:bridge) { create(:ci_bridge) }
before do
command.bridge = bridge
allow(bridge).to receive(:yaml_for_downstream).and_return('the-yaml')
end
it 'returns the content already available in command' do
subject.perform!
expect(pipeline.config_source).to eq 'bridge_source'
expect(command.config_content).to eq 'the-yaml'
end
end
context 'when config is defined in a custom path in the repository' do context 'when config is defined in a custom path in the repository' do
let(:ci_config_path) { 'path/to/config.yml' } let(:ci_config_path) { 'path/to/config.yml' }
let(:config_content_result) do let(:config_content_result) do
......
...@@ -201,8 +201,6 @@ ci_pipelines: ...@@ -201,8 +201,6 @@ ci_pipelines:
- sourced_pipelines - sourced_pipelines
- triggered_by_pipeline - triggered_by_pipeline
- triggered_pipelines - triggered_pipelines
- child_pipelines
- parent_pipeline
- downstream_bridges - downstream_bridges
- job_artifacts - job_artifacts
- vulnerabilities_occurrence_pipelines - vulnerabilities_occurrence_pipelines
......
...@@ -2716,114 +2716,4 @@ describe Ci::Pipeline, :mailer do ...@@ -2716,114 +2716,4 @@ describe Ci::Pipeline, :mailer do
end end
end end
end end
describe '#parent_pipeline' do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when pipeline is triggered by a pipeline from the same project' do
let(:upstream_pipeline) { create(:ci_pipeline, project: pipeline.project) }
before do
create(:ci_sources_pipeline,
source_pipeline: upstream_pipeline,
source_project: project,
pipeline: pipeline,
project: project)
end
it 'returns the parent pipeline' do
expect(pipeline.parent_pipeline).to eq(upstream_pipeline)
end
it 'is child' do
expect(pipeline).to be_child
end
end
context 'when pipeline is triggered by a pipeline from another project' do
let(:upstream_pipeline) { create(:ci_pipeline) }
before do
create(:ci_sources_pipeline,
source_pipeline: upstream_pipeline,
source_project: upstream_pipeline.project,
pipeline: pipeline,
project: project)
end
it 'returns nil' do
expect(pipeline.parent_pipeline).to be_nil
end
it 'is not child' do
expect(pipeline).not_to be_child
end
end
context 'when pipeline is not triggered by a pipeline' do
it 'returns nil' do
expect(pipeline.parent_pipeline).to be_nil
end
it 'is not child' do
expect(pipeline).not_to be_child
end
end
end
describe '#child_pipelines' do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when pipeline triggered other pipelines on same project' do
let(:downstream_pipeline) { create(:ci_pipeline, project: pipeline.project) }
before do
create(:ci_sources_pipeline,
source_pipeline: pipeline,
source_project: pipeline.project,
pipeline: downstream_pipeline,
project: pipeline.project)
end
it 'returns the child pipelines' do
expect(pipeline.child_pipelines).to eq [downstream_pipeline]
end
it 'is parent' do
expect(pipeline).to be_parent
end
end
context 'when pipeline triggered other pipelines on another project' do
let(:downstream_pipeline) { create(:ci_pipeline) }
before do
create(:ci_sources_pipeline,
source_pipeline: pipeline,
source_project: pipeline.project,
pipeline: downstream_pipeline,
project: downstream_pipeline.project)
end
it 'returns empty array' do
expect(pipeline.child_pipelines).to be_empty
end
it 'is not parent' do
expect(pipeline).not_to be_parent
end
end
context 'when pipeline did not trigger any pipelines' do
it 'returns empty array' do
expect(pipeline.child_pipelines).to be_empty
end
it 'is not parent' do
expect(pipeline).not_to be_parent
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:admin) }
let(:ref) { 'refs/heads/master' }
let(:service) { described_class.new(project, user, { ref: ref }) }
context 'custom config content' do
let(:bridge) do
double(:bridge, yaml_for_downstream: <<~YML
rspec:
script: rspec
custom:
script: custom
YML
)
end
subject { service.execute(:push, bridge: bridge) }
it 'creates a pipeline using the content passed in as param' do
expect(subject).to be_persisted
expect(subject.builds.map(&:name)).to eq %w[rspec custom]
expect(subject.config_source).to eq 'bridge_source'
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