debian-mirror-gitlab/spec/services/ci/create_downstream_pipeline_service_spec.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

896 lines
30 KiB
Ruby
Raw Normal View History

2020-03-13 15:44:24 +05:30
# frozen_string_literal: true
require 'spec_helper'
2023-03-04 22:38:38 +05:30
RSpec.describe Ci::CreateDownstreamPipelineService, '#execute', feature_category: :continuous_integration do
2021-06-08 01:23:25 +05:30
include Ci::SourcePipelineHelpers
2022-10-11 01:57:18 +05:30
# Using let_it_be on user and projects for these specs can cause
# spec-ordering failures due to the project-based permissions
# associating them. They should be recreated every time.
let(:user) { create(:user) }
2020-03-13 15:44:24 +05:30
let(:upstream_project) { create(:project, :repository) }
2022-10-11 01:57:18 +05:30
let(:downstream_project) { create(:project, :repository) }
2020-03-13 15:44:24 +05:30
let!(:upstream_pipeline) do
2023-03-04 22:38:38 +05:30
create(:ci_pipeline, :created, project: upstream_project)
2020-03-13 15:44:24 +05:30
end
let(:trigger) do
{
trigger: {
project: downstream_project.full_path,
branch: 'feature'
}
}
end
let(:bridge) do
create(:ci_bridge, status: :pending,
user: user,
options: trigger,
pipeline: upstream_pipeline)
end
let(:service) { described_class.new(upstream_project, user) }
2023-03-04 22:38:38 +05:30
let(:pipeline) { subject.payload }
2020-03-13 15:44:24 +05:30
before do
upstream_project.add_developer(user)
end
2022-07-16 23:28:13 +05:30
subject { service.execute(bridge) }
2020-03-13 15:44:24 +05:30
context 'when downstream project has not been found' do
let(:trigger) do
{ trigger: { project: 'unknown/project' } }
end
it 'does not create a pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-03-13 15:44:24 +05:30
.not_to change { Ci::Pipeline.count }
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to eq("Pre-conditions not met")
2020-03-13 15:44:24 +05:30
end
it 'changes pipeline bridge job status to failed' do
2022-07-16 23:28:13 +05:30
subject
2020-03-13 15:44:24 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason)
.to eq 'downstream_bridge_project_not_found'
end
end
context 'when user can not access downstream project' do
it 'does not create a new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-03-13 15:44:24 +05:30
.not_to change { Ci::Pipeline.count }
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to eq("Pre-conditions not met")
2020-03-13 15:44:24 +05:30
end
2023-03-04 22:38:38 +05:30
it 'changes status of the bridge build to failed' do
2022-07-16 23:28:13 +05:30
subject
2020-03-13 15:44:24 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason)
.to eq 'downstream_bridge_project_not_found'
end
end
context 'when user does not have access to create pipeline' do
before do
downstream_project.add_guest(user)
end
it 'does not create a new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-03-13 15:44:24 +05:30
.not_to change { Ci::Pipeline.count }
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to eq("Pre-conditions not met")
2020-03-13 15:44:24 +05:30
end
2023-03-04 22:38:38 +05:30
it 'changes status of the bridge build to failed' do
2022-07-16 23:28:13 +05:30
subject
2020-03-13 15:44:24 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq 'insufficient_bridge_permissions'
end
end
context 'when user can create pipeline in a downstream project' do
let(:stub_config) { true }
before do
downstream_project.add_developer(user)
stub_ci_pipeline_yaml_file(YAML.dump(rspec: { script: 'rspec' })) if stub_config
end
it 'creates only one new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-03-13 15:44:24 +05:30
.to change { Ci::Pipeline.count }.by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_success
2020-03-13 15:44:24 +05:30
end
it 'creates a new pipeline in a downstream project' do
expect(pipeline.user).to eq bridge.user
expect(pipeline.project).to eq downstream_project
2023-03-04 22:38:38 +05:30
expect(bridge.reload.sourced_pipeline.pipeline).to eq pipeline
2020-03-13 15:44:24 +05:30
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
2022-07-16 23:28:13 +05:30
it_behaves_like 'logs downstream pipeline creation' do
2023-03-04 22:38:38 +05:30
let(:downstream_pipeline) { pipeline }
2022-07-16 23:28:13 +05:30
let(:expected_root_pipeline) { upstream_pipeline }
let(:expected_hierarchy_size) { 2 }
let(:expected_downstream_relationship) { :multi_project }
end
2020-04-08 14:13:33 +05:30
it 'updates bridge status when downstream pipeline gets processed' do
2021-04-29 21:17:54 +05:30
expect(pipeline.reload).to be_created
2020-03-13 15:44:24 +05:30
expect(bridge.reload).to be_success
end
2023-03-04 22:38:38 +05:30
it 'triggers the upstream pipeline duration calculation', :sidekiq_inline do
expect { subject }
.to change { upstream_pipeline.reload.duration }.from(nil).to(an_instance_of(Integer))
end
context 'when bridge job has already any downstream pipeline' do
2020-04-08 14:13:33 +05:30
before do
2023-03-04 22:38:38 +05:30
bridge.create_sourced_pipeline!(
2020-04-08 14:13:33 +05:30
source_pipeline: bridge.pipeline,
source_project: bridge.project,
project: bridge.project,
pipeline: create(:ci_pipeline, project: bridge.project)
)
end
it 'logs an error and exits' do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(
2020-11-24 15:15:51 +05:30
instance_of(described_class::DuplicateDownstreamPipelineError),
2020-04-08 14:13:33 +05:30
bridge_id: bridge.id, project_id: bridge.project.id)
.and_call_original
expect(Ci::CreatePipelineService).not_to receive(:new)
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to eq("Already has a downstream pipeline")
end
2020-04-08 14:13:33 +05:30
end
2020-03-13 15:44:24 +05:30
context 'when target ref is not specified' do
let(:trigger) do
{ trigger: { project: downstream_project.full_path } }
end
it 'is using default branch name' do
expect(pipeline.ref).to eq 'master'
end
end
2020-04-08 14:13:33 +05:30
context 'when downstream pipeline has yaml configuration error' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(job: { invalid: 'yaml' }))
end
it 'creates only one new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-04-08 14:13:33 +05:30
.to change { Ci::Pipeline.count }.by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to match_array(["jobs job config should implement a script: or a trigger: keyword"])
2020-04-08 14:13:33 +05:30
end
it 'creates a new pipeline in a downstream project' do
expect(pipeline.user).to eq bridge.user
expect(pipeline.project).to eq downstream_project
2023-03-04 22:38:38 +05:30
expect(bridge.reload.sourced_pipeline.pipeline).to eq pipeline
2020-04-08 14:13:33 +05:30
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
2023-03-04 22:38:38 +05:30
it 'updates the bridge status when downstream pipeline gets processed' do
2020-04-08 14:13:33 +05:30
expect(pipeline.reload).to be_failed
expect(bridge.reload).to be_failed
end
end
2020-11-24 15:15:51 +05:30
context 'when downstream project is the same as the upstream project' do
2020-03-13 15:44:24 +05:30
let(:trigger) do
{ trigger: { project: upstream_project.full_path } }
end
context 'detects a circular dependency' do
it 'does not create a new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-03-13 15:44:24 +05:30
.not_to change { Ci::Pipeline.count }
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to eq("Pre-conditions not met")
2020-03-13 15:44:24 +05:30
end
it 'changes status of the bridge build' do
2022-07-16 23:28:13 +05:30
subject
2020-03-13 15:44:24 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq 'invalid_bridge_trigger'
end
end
context 'when "include" is provided' do
let(:file_content) do
YAML.dump(
rspec: { script: 'rspec' },
echo: { script: 'echo' })
end
shared_examples 'creates a child pipeline' do
it 'creates only one new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-03-13 15:44:24 +05:30
.to change { Ci::Pipeline.count }.by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_success
2020-03-13 15:44:24 +05:30
end
it 'creates a child pipeline in the same project' do
2020-04-22 19:07:51 +05:30
expect(pipeline.builds.map(&:name)).to match_array(%w[rspec echo])
2020-03-13 15:44:24 +05:30
expect(pipeline.user).to eq bridge.user
expect(pipeline.project).to eq bridge.project
2023-03-04 22:38:38 +05:30
expect(bridge.reload.sourced_pipeline.pipeline).to eq pipeline
2020-03-13 15:44:24 +05:30
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
2023-03-04 22:38:38 +05:30
it 'updates bridge status when downstream pipeline gets processed' do
2021-04-29 21:17:54 +05:30
expect(pipeline.reload).to be_created
2020-03-13 15:44:24 +05:30
expect(bridge.reload).to be_success
end
it 'propagates parent pipeline settings to the child pipeline' do
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
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'
2022-07-16 23:28:13 +05:30
it_behaves_like 'logs downstream pipeline creation' do
2023-03-04 22:38:38 +05:30
let(:downstream_pipeline) { pipeline }
2022-07-16 23:28:13 +05:30
let(:expected_root_pipeline) { upstream_pipeline }
let(:expected_hierarchy_size) { 2 }
let(:expected_downstream_relationship) { :parent_child }
end
2020-04-08 14:13:33 +05:30
it 'updates the bridge job to success' do
2022-07-16 23:28:13 +05:30
expect { subject }.to change { bridge.status }.to 'success'
2023-03-04 22:38:38 +05:30
expect(subject).to be_success
2020-04-08 14:13:33 +05:30
end
context 'when bridge uses "depend" strategy' do
let(:trigger) do
{
trigger: { include: 'child-pipeline.yml', strategy: 'depend' }
}
end
2023-03-04 22:38:38 +05:30
it 'update the bridge job to running status' do
expect { subject }.to change { bridge.status }.from('pending').to('running')
expect(subject).to be_success
2020-04-08 14:13:33 +05:30
end
end
2020-03-13 15:44:24 +05:30
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
it_behaves_like 'creates a child pipeline'
end
context 'when the parent is a merge request pipeline' do
let(:merge_request) { create(:merge_request, source_project: bridge.project, target_project: bridge.project) }
let(:file_content) do
YAML.dump(
workflow: { rules: [{ if: '$CI_MERGE_REQUEST_ID' }] },
rspec: { script: 'rspec' },
echo: { script: 'echo' })
end
before do
bridge.pipeline.update!(source: :merge_request_event, merge_request: merge_request)
end
it_behaves_like 'creates a child pipeline'
it 'propagates the merge request to the child pipeline' do
expect(pipeline.merge_request).to eq(merge_request)
expect(pipeline).to be_merge_request
end
end
2020-11-24 15:15:51 +05:30
context 'when upstream pipeline has a parent pipeline' do
before do
2020-03-13 15:44:24 +05:30
create(:ci_sources_pipeline,
source_pipeline: create(:ci_pipeline, project: upstream_pipeline.project),
pipeline: upstream_pipeline
)
end
2020-11-24 15:15:51 +05:30
it 'creates the pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-11-24 15:15:51 +05:30
.to change { Ci::Pipeline.count }.by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_success
2020-11-24 15:15:51 +05:30
expect(bridge.reload).to be_success
end
2022-07-16 23:28:13 +05:30
it_behaves_like 'logs downstream pipeline creation' do
2023-03-04 22:38:38 +05:30
let(:downstream_pipeline) { pipeline }
2022-07-16 23:28:13 +05:30
let(:expected_root_pipeline) { upstream_pipeline.parent_pipeline }
let(:expected_hierarchy_size) { 3 }
let(:expected_downstream_relationship) { :parent_child }
end
2020-11-24 15:15:51 +05:30
end
context 'when upstream pipeline has a parent pipeline, which has a parent pipeline' do
2020-03-13 15:44:24 +05:30
before do
2020-11-24 15:15:51 +05:30
parent_of_upstream_pipeline = create(:ci_pipeline, project: upstream_pipeline.project)
create(:ci_sources_pipeline,
source_pipeline: create(:ci_pipeline, project: upstream_pipeline.project),
pipeline: parent_of_upstream_pipeline
)
create(:ci_sources_pipeline,
source_pipeline: parent_of_upstream_pipeline,
pipeline: upstream_pipeline
)
2020-03-13 15:44:24 +05:30
end
2020-11-24 15:15:51 +05:30
it 'does not create a second descendant pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-03-13 15:44:24 +05:30
.not_to change { Ci::Pipeline.count }
expect(bridge.reload).to be_failed
2020-11-24 15:15:51 +05:30
expect(bridge.failure_reason).to eq 'reached_max_descendant_pipelines_depth'
end
end
context 'when upstream pipeline has two level upstream pipelines from different projects' do
before do
upstream_of_upstream_of_upstream_pipeline = create(:ci_pipeline)
upstream_of_upstream_pipeline = create(:ci_pipeline)
create(:ci_sources_pipeline,
source_pipeline: upstream_of_upstream_of_upstream_pipeline,
pipeline: upstream_of_upstream_pipeline
)
create(:ci_sources_pipeline,
source_pipeline: upstream_of_upstream_pipeline,
pipeline: upstream_pipeline
)
end
it 'create the pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }.to change { Ci::Pipeline.count }.by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_success
2020-03-13 15:44:24 +05:30
end
end
2021-03-08 18:12:59 +05:30
context 'when downstream project does not allow user-defined variables for child pipelines' do
before do
bridge.yaml_variables = [{ key: 'BRIDGE', value: '$PIPELINE_VARIABLE-var', public: true }]
upstream_pipeline.project.update!(restrict_user_defined_variables: true)
end
it 'creates a new pipeline allowing variables to be passed downstream' do
2022-07-16 23:28:13 +05:30
expect { subject }.to change { Ci::Pipeline.count }.by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_success
2021-03-08 18:12:59 +05:30
end
it 'passes variables downstream from the bridge' do
pipeline.variables.map(&:key).tap do |variables|
expect(variables).to include 'BRIDGE'
end
end
end
2021-09-04 01:27:46 +05:30
context 'when multi-project pipeline runs from child pipelines bridge job' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(rspec: { script: 'rspec' }))
end
# instantiate new service, to clear memoized values from child pipeline run
subject(:execute_with_trigger_project_bridge) do
described_class.new(upstream_project, user).execute(trigger_project_bridge)
end
let!(:child_pipeline) do
service.execute(bridge)
bridge.downstream_pipeline
end
let!(:trigger_downstream_project) do
{
trigger: {
project: downstream_project.full_path,
branch: 'feature'
}
}
end
let!(:trigger_project_bridge) do
create(
2022-10-11 01:57:18 +05:30
:ci_bridge, status: :pending, user: user, options: trigger_downstream_project, pipeline: child_pipeline
2021-09-04 01:27:46 +05:30
)
end
it 'creates a new pipeline' do
expect { execute_with_trigger_project_bridge }
.to change { Ci::Pipeline.count }.by(1)
new_pipeline = trigger_project_bridge.downstream_pipeline
expect(new_pipeline.child?).to eq(false)
expect(new_pipeline.triggered_by_pipeline).to eq child_pipeline
expect(trigger_project_bridge.reload).not_to be_failed
end
end
2020-03-13 15:44:24 +05:30
end
end
2022-04-04 11:22:00 +05:30
describe 'cyclical dependency detection' do
shared_examples 'detects cyclical pipelines' do
it 'does not create a new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2022-04-04 11:22:00 +05:30
.not_to change { Ci::Pipeline.count }
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to eq("Pre-conditions not met")
2022-04-04 11:22:00 +05:30
end
2021-06-08 01:23:25 +05:30
2022-04-04 11:22:00 +05:30
it 'changes status of the bridge build' do
2022-07-16 23:28:13 +05:30
subject
2022-04-04 11:22:00 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq 'pipeline_loop_detected'
end
2021-06-08 01:23:25 +05:30
end
2022-04-04 11:22:00 +05:30
shared_examples 'passes cyclical pipeline precondition' do
it 'creates a new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2022-04-04 11:22:00 +05:30
.to change { Ci::Pipeline.count }
2023-03-04 22:38:38 +05:30
expect(subject).to be_success
2022-04-04 11:22:00 +05:30
end
it 'expect bridge build not to be failed' do
2022-07-16 23:28:13 +05:30
subject
2022-04-04 11:22:00 +05:30
expect(bridge.reload).not_to be_failed
end
2021-06-08 01:23:25 +05:30
end
2022-04-04 11:22:00 +05:30
context 'when pipeline ancestry contains 2 cycles of dependencies' do
before do
# A(push on master) -> B(pipeline on master) -> A(push on master) ->
# B(pipeline on master) -> A(push on master)
pipeline_1 = create(:ci_pipeline, project: upstream_project, source: :push)
pipeline_2 = create(:ci_pipeline, project: downstream_project, source: :pipeline)
pipeline_3 = create(:ci_pipeline, project: upstream_project, source: :push)
pipeline_4 = create(:ci_pipeline, project: downstream_project, source: :pipeline)
create_source_pipeline(pipeline_1, pipeline_2)
create_source_pipeline(pipeline_2, pipeline_3)
create_source_pipeline(pipeline_3, pipeline_4)
create_source_pipeline(pipeline_4, upstream_pipeline)
end
it_behaves_like 'detects cyclical pipelines'
2021-06-08 01:23:25 +05:30
end
2022-04-04 11:22:00 +05:30
context 'when source in the ancestry differ' do
2021-06-08 01:23:25 +05:30
before do
2022-04-04 11:22:00 +05:30
# A(push on master) -> B(pipeline on master) -> A(pipeline on master)
pipeline_1 = create(:ci_pipeline, project: upstream_project, source: :push)
pipeline_2 = create(:ci_pipeline, project: downstream_project, source: :pipeline)
upstream_pipeline.update!(source: :pipeline)
create_source_pipeline(pipeline_1, pipeline_2)
create_source_pipeline(pipeline_2, upstream_pipeline)
2021-06-08 01:23:25 +05:30
end
2022-04-04 11:22:00 +05:30
it_behaves_like 'passes cyclical pipeline precondition'
end
context 'when ref in the ancestry differ' do
before do
# A(push on master) -> B(pipeline on master) -> A(push on feature-1)
pipeline_1 = create(:ci_pipeline, ref: 'master', project: upstream_project, source: :push)
pipeline_2 = create(:ci_pipeline, ref: 'master', project: downstream_project, source: :pipeline)
upstream_pipeline.update!(ref: 'feature-1')
create_source_pipeline(pipeline_1, pipeline_2)
create_source_pipeline(pipeline_2, upstream_pipeline)
2021-06-08 01:23:25 +05:30
end
2022-04-04 11:22:00 +05:30
it_behaves_like 'passes cyclical pipeline precondition'
end
2021-06-08 01:23:25 +05:30
2022-04-04 11:22:00 +05:30
context 'when only 1 cycle is detected' do
before do
# A(push on master) -> B(pipeline on master) -> A(push on master)
pipeline_1 = create(:ci_pipeline, ref: 'master', project: upstream_project, source: :push)
pipeline_2 = create(:ci_pipeline, ref: 'master', project: downstream_project, source: :pipeline)
create_source_pipeline(pipeline_1, pipeline_2)
create_source_pipeline(pipeline_2, upstream_pipeline)
2021-06-08 01:23:25 +05:30
end
2022-04-04 11:22:00 +05:30
it_behaves_like 'passes cyclical pipeline precondition'
2021-06-08 01:23:25 +05:30
end
end
2020-04-08 14:13:33 +05:30
context 'when downstream pipeline creation errors out' do
let(:stub_config) { false }
before do
stub_ci_pipeline_yaml_file(YAML.dump(invalid: { yaml: 'error' }))
end
it 'creates only one new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-04-08 14:13:33 +05:30
.to change { Ci::Pipeline.count }.by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to match_array(["jobs invalid config should implement a script: or a trigger: keyword"])
2020-04-08 14:13:33 +05:30
end
it 'creates a new pipeline in the downstream project' do
expect(pipeline.user).to eq bridge.user
expect(pipeline.project).to eq downstream_project
end
it 'drops the bridge' do
expect(pipeline.reload).to be_failed
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed')
end
end
context 'when bridge job status update raises state machine errors' do
let(:stub_config) { false }
before do
stub_ci_pipeline_yaml_file(YAML.dump(invalid: { yaml: 'error' }))
bridge.drop!
end
2023-03-04 22:38:38 +05:30
it 'returns the error' do
expect { subject }.not_to change(downstream_project.ci_pipelines, :count)
expect(subject).to be_error
expect(subject.message).to eq('Can not run the bridge')
2020-04-08 14:13:33 +05:30
end
end
2020-03-13 15:44:24 +05:30
context 'when bridge job has YAML variables defined' do
before do
bridge.yaml_variables = [{ key: 'BRIDGE', value: 'var', public: true }]
end
it 'passes bridge variables to downstream pipeline' do
expect(pipeline.variables.first)
.to have_attributes(key: 'BRIDGE', value: 'var')
end
end
context 'when pipeline variables are defined' do
before do
2020-11-24 15:15:51 +05:30
upstream_pipeline.variables.create!(key: 'PIPELINE_VARIABLE', value: 'my-value')
2020-03-13 15:44:24 +05:30
end
it 'does not pass pipeline variables directly downstream' do
pipeline.variables.map(&:key).tap do |variables|
expect(variables).not_to include 'PIPELINE_VARIABLE'
end
end
context 'when using YAML variables interpolation' do
before do
bridge.yaml_variables = [{ key: 'BRIDGE', value: '$PIPELINE_VARIABLE-var', public: true }]
end
it 'makes it possible to pass pipeline variable downstream' do
pipeline.variables.find_by(key: 'BRIDGE').tap do |variable|
expect(variable.value).to eq 'my-value-var'
end
end
2021-03-08 18:12:59 +05:30
context 'when downstream project does not allow user-defined variables for multi-project pipelines' do
before do
downstream_project.update!(restrict_user_defined_variables: true)
end
it 'does not create a new pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2021-03-08 18:12:59 +05:30
.not_to change { Ci::Pipeline.count }
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to match_array(["Insufficient permissions to set pipeline variables"])
2021-03-08 18:12:59 +05:30
end
it 'ignores variables passed downstream from the bridge' do
pipeline.variables.map(&:key).tap do |variables|
expect(variables).not_to include 'BRIDGE'
end
end
it 'sets errors', :aggregate_failures do
2022-07-16 23:28:13 +05:30
subject
2021-03-08 18:12:59 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed')
expect(bridge.options[:downstream_errors]).to eq(['Insufficient permissions to set pipeline variables'])
end
end
2020-03-13 15:44:24 +05:30
end
end
# TODO: Move this context into a feature spec that uses
# multiple pipeline processing services. Location TBD in:
# https://gitlab.com/gitlab-org/gitlab/issues/36216
2023-03-04 22:38:38 +05:30
context 'when configured with bridge job rules', :sidekiq_inline do
2020-03-13 15:44:24 +05:30
before do
stub_ci_pipeline_yaml_file(config)
2022-03-02 08:16:31 +05:30
downstream_project.add_maintainer(upstream_project.first_owner)
2020-03-13 15:44:24 +05:30
end
let(:config) do
<<-EOY
hello:
script: echo world
bridge-job:
rules:
- if: $CI_COMMIT_REF_NAME == "master"
trigger:
project: #{downstream_project.full_path}
branch: master
EOY
end
let(:primary_pipeline) do
2022-03-02 08:16:31 +05:30
Ci::CreatePipelineService.new(upstream_project, upstream_project.first_owner, { ref: 'master' })
2020-03-13 15:44:24 +05:30
.execute(:push, save_on_errors: false)
2021-10-27 15:23:28 +05:30
.payload
2020-03-13 15:44:24 +05:30
end
let(:bridge) { primary_pipeline.processables.find_by(name: 'bridge-job') }
2022-03-02 08:16:31 +05:30
let(:service) { described_class.new(upstream_project, upstream_project.first_owner) }
2020-03-13 15:44:24 +05:30
context 'that include the bridge job' do
it 'creates the downstream pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }
2020-03-13 15:44:24 +05:30
.to change(downstream_project.ci_pipelines, :count).by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to eq("Already has a downstream pipeline")
2020-03-13 15:44:24 +05:30
end
end
end
context 'when user does not have access to push protected branch of downstream project' do
before do
create(:protected_branch, :maintainers_can_push,
project: downstream_project, name: 'feature')
end
it 'changes status of the bridge build' do
2022-07-16 23:28:13 +05:30
subject
2020-03-13 15:44:24 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq 'insufficient_bridge_permissions'
end
end
2020-04-22 19:07:51 +05:30
context 'when there is no such branch in downstream project' do
let(:trigger) do
{
trigger: {
project: downstream_project.full_path,
branch: 'invalid_branch'
}
}
end
it 'does not create a pipeline and drops the bridge' do
2022-07-16 23:28:13 +05:30
expect { subject }.not_to change(downstream_project.ci_pipelines, :count)
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to match_array(["Reference not found"])
2020-04-22 19:07:51 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed')
2020-06-23 00:09:42 +05:30
expect(bridge.options[:downstream_errors]).to eq(['Reference not found'])
2020-04-22 19:07:51 +05:30
end
end
context 'when downstream pipeline has a branch rule and does not satisfy' do
before do
stub_ci_pipeline_yaml_file(config)
end
let(:config) do
<<-EOY
hello:
script: echo world
only:
- invalid_branch
EOY
end
it 'does not create a pipeline and drops the bridge' do
2022-07-16 23:28:13 +05:30
expect { subject }.not_to change(downstream_project.ci_pipelines, :count)
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
2023-03-17 16:20:25 +05:30
expect(subject.message).to match_array(['Pipeline will not run for the selected trigger. ' \
'The rules configuration prevented any jobs from being added to the pipeline.'])
2020-06-23 00:09:42 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed')
2023-03-17 16:20:25 +05:30
expect(bridge.options[:downstream_errors]).to match_array(['Pipeline will not run for the selected trigger. ' \
'The rules configuration prevented any jobs from being added to the pipeline.'])
2020-06-23 00:09:42 +05:30
end
end
context 'when downstream pipeline has invalid YAML' do
before do
stub_ci_pipeline_yaml_file(config)
end
let(:config) do
<<-EOY
test:
stage: testx
script: echo 1
EOY
end
it 'creates the pipeline but drops the bridge' do
2022-07-16 23:28:13 +05:30
expect { subject }.to change(downstream_project.ci_pipelines, :count).by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_error
expect(subject.message).to eq(
["test job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post"]
)
2020-04-22 19:07:51 +05:30
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed')
2020-06-23 00:09:42 +05:30
expect(bridge.options[:downstream_errors]).to eq(
['test job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post']
)
2020-04-22 19:07:51 +05:30
end
end
2021-01-29 00:20:46 +05:30
context 'when downstream pipeline has workflow rule' do
before do
stub_ci_pipeline_yaml_file(config)
end
let(:config) do
<<-EOY
workflow:
rules:
- if: $my_var
regular-job:
script: 'echo Hello, World!'
EOY
end
context 'when passing the required variable' do
before do
bridge.yaml_variables = [{ key: 'my_var', value: 'var', public: true }]
end
it 'creates the pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }.to change(downstream_project.ci_pipelines, :count).by(1)
2023-03-04 22:38:38 +05:30
expect(subject).to be_success
2021-01-29 00:20:46 +05:30
expect(bridge.reload).to be_success
end
end
context 'when not passing the required variable' do
it 'does not create the pipeline' do
2022-07-16 23:28:13 +05:30
expect { subject }.not_to change(downstream_project.ci_pipelines, :count)
2021-01-29 00:20:46 +05:30
end
end
end
2022-10-11 01:57:18 +05:30
context 'when a downstream pipeline has sibling pipelines' do
it_behaves_like 'logs downstream pipeline creation' do
2023-03-04 22:38:38 +05:30
let(:downstream_pipeline) { pipeline }
2022-10-11 01:57:18 +05:30
let(:expected_root_pipeline) { upstream_pipeline }
let(:expected_downstream_relationship) { :multi_project }
# New downstream, plus upstream, plus two children of upstream created below
let(:expected_hierarchy_size) { 4 }
before do
create_list(:ci_pipeline, 2, child_of: upstream_pipeline)
end
end
end
context 'when the pipeline tree is too large' do
let_it_be(:parent) { create(:ci_pipeline) }
let_it_be(:child) { create(:ci_pipeline, child_of: parent) }
let_it_be(:sibling) { create(:ci_pipeline, child_of: parent) }
2023-03-04 22:38:38 +05:30
let(:project) { build(:project, :repository) }
2022-10-11 01:57:18 +05:30
let(:bridge) do
2023-03-04 22:38:38 +05:30
create(:ci_bridge, status: :pending, user: user, options: trigger, pipeline: child, project: project)
2022-10-11 01:57:18 +05:30
end
2023-03-04 22:38:38 +05:30
context 'when limit was specified by admin' do
before do
project.actual_limits.update!(pipeline_hierarchy_size: 3)
end
it 'does not create a new pipeline' do
expect { subject }.not_to change { Ci::Pipeline.count }
end
it 'drops the trigger job with an explanatory reason' do
subject
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq('reached_max_pipeline_hierarchy_size')
end
2022-10-11 01:57:18 +05:30
end
2023-03-04 22:38:38 +05:30
context 'when there was no limit specified by admin' do
before do
allow(bridge.pipeline).to receive(:complete_hierarchy_count).and_return(1000)
end
2022-10-11 01:57:18 +05:30
2023-03-04 22:38:38 +05:30
context 'when pipeline count reaches the default limit of 1000' do
it 'does not create a new pipeline' do
expect { subject }.not_to change { Ci::Pipeline.count }
expect(subject).to be_error
expect(subject.message).to eq("Pre-conditions not met")
end
it 'drops the trigger job with an explanatory reason' do
subject
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq('reached_max_pipeline_hierarchy_size')
end
end
2022-10-11 01:57:18 +05:30
end
end
2020-03-13 15:44:24 +05:30
end
end