Commit c1a4a20c authored by Fabio Pitino's avatar Fabio Pitino

Port Ci::Bridge to Core

Move most of the model and specs
parent 54c2a03b
...@@ -4,19 +4,78 @@ module Ci ...@@ -4,19 +4,78 @@ module Ci
class Bridge < Ci::Processable class Bridge < Ci::Processable
include Ci::Contextable include Ci::Contextable
include Ci::PipelineDelegator include Ci::PipelineDelegator
include Ci::Metadatable
include Importable include Importable
include AfterCommitQueue include AfterCommitQueue
include HasRef include HasRef
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
InvalidBridgeTypeError = Class.new(StandardError)
belongs_to :project belongs_to :project
belongs_to :trigger_request belongs_to :trigger_request
has_many :sourced_pipelines, class_name: "::Ci::Sources::Pipeline",
foreign_key: :source_job_id
validates :ref, presence: true validates :ref, presence: true
# rubocop:disable Cop/ActiveRecordSerialize
serialize :options
serialize :yaml_variables, ::Gitlab::Serializer::Ci::Variables
# rubocop:enable Cop/ActiveRecordSerialize
state_machine :status do
event :manual do
transition all => :manual
end
event :scheduled do
transition all => :scheduled
end
end
def self.retry(bridge, current_user) def self.retry(bridge, current_user)
raise NotImplementedError raise NotImplementedError
end end
def inherit_status_from_downstream!(pipeline)
case pipeline.status
when 'success'
self.success!
when 'failed', 'canceled', 'skipped'
self.drop!
else
false
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
strong_memoize(:downstream_project) do
if downstream_project_path
::Project.find_by_full_path(downstream_project_path)
elsif triggers_child_pipeline?
project
end
end
end
def downstream_project_path
strong_memoize(:downstream_project_path) do
options&.dig(:trigger, :project)
end
end
def triggers_child_pipeline?
yaml_for_downstream.present?
end
def tags def tags
[:bridge] [:bridge]
end end
...@@ -55,7 +114,68 @@ module Ci ...@@ -55,7 +114,68 @@ module Ci
end end
def yaml_for_downstream def yaml_for_downstream
nil strong_memoize(:yaml_for_downstream) do
includes = options&.dig(:trigger, :include)
YAML.dump('include' => includes) if includes
end
end
def target_ref
branch = options&.dig(:trigger, :branch)
return unless branch
scoped_variables.to_runner_variables.yield_self do |all_variables|
::ExpandVariables.expand(branch, all_variables)
end
end
def dependent?
strong_memoize(:dependent) do
options&.dig(:trigger, :strategy) == 'depend'
end
end
def downstream_variables
variables = scoped_variables.concat(pipeline.persisted_variables)
variables.to_runner_variables.yield_self do |all_variables|
yaml_variables.to_a.map do |hash|
{ key: hash[:key], value: ::ExpandVariables.expand(hash[:value], all_variables) }
end
end
end
private
def cross_project_params
{
project: downstream_project,
source: :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
......
...@@ -4,22 +4,9 @@ module EE ...@@ -4,22 +4,9 @@ module EE
module Ci module Ci
module Bridge module Bridge
extend ActiveSupport::Concern extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
include ::Gitlab::Utils::StrongMemoize
InvalidBridgeTypeError = Class.new(StandardError)
prepended do prepended do
include ::Ci::Metadatable
# rubocop:disable Cop/ActiveRecordSerialize
serialize :options
serialize :yaml_variables, ::Gitlab::Serializer::Ci::Variables
# rubocop:enable Cop/ActiveRecordSerialize
belongs_to :upstream_pipeline, class_name: "::Ci::Pipeline" belongs_to :upstream_pipeline, class_name: "::Ci::Pipeline"
has_many :sourced_pipelines, class_name: "::Ci::Sources::Pipeline",
foreign_key: :source_job_id
state_machine :status do state_machine :status do
after_transition created: :pending do |bridge| after_transition created: :pending do |bridge|
...@@ -37,14 +24,6 @@ module EE ...@@ -37,14 +24,6 @@ module EE
bridge.subscribe_to_upstream! bridge.subscribe_to_upstream!
end end
end end
event :manual do
transition all => :manual
end
event :scheduled do
transition all => :scheduled
end
end end
end end
...@@ -84,129 +63,17 @@ module EE ...@@ -84,129 +63,17 @@ module EE
end end
end end
def inherit_status_from_downstream!(pipeline)
case pipeline.status
when 'success'
self.success!
when 'failed', 'canceled', 'skipped'
self.drop!
else
false
end
end
def target_user
self.user
end
def target_project
downstream_project || upstream_project
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
strong_memoize(:downstream_project) do
if downstream_project_path
::Project.find_by_full_path(downstream_project_path)
elsif triggers_child_pipeline?
project
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) upstream_project_path && ::Project.find_by_full_path(upstream_project_path)
end end
end end
def target_ref
branch = options&.dig(:trigger, :branch)
return unless branch
scoped_variables.to_runner_variables.yield_self do |all_variables|
::ExpandVariables.expand(branch, all_variables)
end
end
def dependent?
strong_memoize(:dependent) do
options&.dig(:trigger, :strategy) == 'depend'
end
end
def downstream_variables
variables = scoped_variables.concat(pipeline.persisted_variables)
variables.to_runner_variables.yield_self do |all_variables|
yaml_variables.to_a.map do |hash|
{ key: hash[:key], value: ::ExpandVariables.expand(hash[:value], all_variables) }
end
end
end
def downstream_project_path
strong_memoize(:downstream_project_path) do
options&.dig(:trigger, :project)
end
end
def upstream_project_path def upstream_project_path
strong_memoize(:upstream_project_path) do strong_memoize(:upstream_project_path) do
options&.dig(:bridge_needs, :pipeline) options&.dig(:bridge_needs, :pipeline)
end end
end end
private
def cross_project_params
{
project: downstream_project,
source: :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
...@@ -21,10 +21,6 @@ describe Ci::Bridge do ...@@ -21,10 +21,6 @@ describe Ci::Bridge do
expect(bridge).to belong_to(:upstream_pipeline) expect(bridge).to belong_to(:upstream_pipeline)
end end
it 'has many sourced pipelines' do
expect(bridge).to have_many(:sourced_pipelines)
end
describe 'state machine transitions' do describe 'state machine transitions' do
context 'when bridge points towards downstream' do context 'when bridge points towards downstream' do
it 'does not subscribe to upstream project' do it 'does not subscribe to upstream project' do
...@@ -32,12 +28,6 @@ describe Ci::Bridge do ...@@ -32,12 +28,6 @@ describe Ci::Bridge do
bridge.enqueue! bridge.enqueue!
end end
it 'schedules downstream pipeline creation' do
expect(bridge).to receive(:schedule_downstream_pipeline!)
bridge.enqueue!
end
end end
context 'when bridge points towards upstream' do context 'when bridge points towards upstream' do
...@@ -107,244 +97,4 @@ describe Ci::Bridge do ...@@ -107,244 +97,4 @@ describe Ci::Bridge do
end end
end end
end end
describe '#inherit_status_from_downstream!' do
let(:downstream_pipeline) { build(:ci_pipeline, status: downstream_status) }
before do
bridge.status = 'pending'
create(:ci_sources_pipeline, pipeline: downstream_pipeline, source_job: bridge)
end
subject { bridge.inherit_status_from_downstream!(downstream_pipeline) }
context 'when status is not supported' do
(::Ci::Pipeline::AVAILABLE_STATUSES - ::Ci::Pipeline::COMPLETED_STATUSES).map(&:to_s).each do |status|
context "when status is #{status}" do
let(:downstream_status) { status }
it 'returns false' do
expect(subject).to eq(false)
end
it 'does not change the bridge status' do
expect { subject }.not_to change { bridge.status }.from('pending')
end
end
end
end
context 'when status is supported' do
using RSpec::Parameterized::TableSyntax
where(:downstream_status, :upstream_status) do
[
%w[success success],
*::Ci::Pipeline.completed_statuses.without(:success).map { |status| [status.to_s, 'failed'] }
]
end
with_them do
it 'inherits the downstream status' do
expect { subject }.to change { bridge.status }.from('pending').to(upstream_status)
end
end
end
end
describe '#target_user' do
it 'is the same as a user who created a pipeline' do
expect(bridge.target_user).to eq bridge.user
end
end
describe '#target_project' do
context 'when trigger is defined' do
it 'returns a full path of a project' do
expect(bridge.target_project).to eq target_project
end
end
context 'when trigger does not have project defined' do
let(:options) { { trigger: {} } }
it 'returns nil' do
expect(bridge.target_project).to be_nil
end
end
end
describe '#target_ref' do
context 'when trigger is defined' do
it 'returns a ref name' do
expect(bridge.target_ref).to eq 'master'
end
context 'when using variable expansion' do
let(:options) { { trigger: { project: 'my/project', branch: '$BRIDGE-master' } } }
it 'correctly expands variables' do
expect(bridge.target_ref).to eq('cross-master')
end
end
end
context 'when trigger does not have project defined' do
let(:options) { nil }
it 'returns nil' do
expect(bridge.target_ref).to be_nil
end
end
end
describe '#dependent?' do
subject { bridge.dependent? }
context 'when bridge has strategy depend' do
let(:options) { { trigger: { project: 'my/project', strategy: 'depend' } } }
it { is_expected.to be true }
end
context 'when bridge does not have strategy depend' do
it { is_expected.to be false }
end
end
describe '#yaml_variables' do
it 'returns YAML variables' do
expect(bridge.yaml_variables)
.to include(key: 'BRIDGE', value: 'cross', public: true)
end
end
describe '#downstream_variables' do
it 'returns variables that are going to be passed downstream' do
expect(bridge.downstream_variables)
.to include(key: 'BRIDGE', value: 'cross')
end
context 'when using variables interpolation' do
let(:yaml_variables) do
[
{
key: 'EXPANDED',
value: '$BRIDGE-bridge',
public: true
},
{
key: 'UPSTREAM_CI_PIPELINE_ID',
value: '$CI_PIPELINE_ID',
public: true
},
{
key: 'UPSTREAM_CI_PIPELINE_URL',
value: '$CI_PIPELINE_URL',
public: true
}
]
end
before do
bridge.yaml_variables.concat(yaml_variables)
end
it 'correctly expands variables with interpolation' do
expanded_values = pipeline
.persisted_variables
.to_hash
.transform_keys { |key| "UPSTREAM_#{key}" }
.map { |key, value| { key: key, value: value } }
.push(key: 'EXPANDED', value: 'cross-bridge')
expect(bridge.downstream_variables)
.to match(a_collection_including(*expanded_values))
end
end
context 'when recursive interpolation has been used' do
before do
bridge.yaml_variables << { key: 'EXPANDED', value: '$EXPANDED', public: true }
end
it 'does not expand variable recursively' do
expect(bridge.downstream_variables)
.to include(key: 'EXPANDED', value: '$EXPANDED')
end
end
end
describe 'metadata support' do
it 'reads YAML variables from metadata' do
expect(bridge.yaml_variables).not_to be_empty
expect(bridge.metadata).to be_a Ci::BuildMetadata
expect(bridge.read_attribute(:yaml_variables)).to be_nil
expect(bridge.metadata.config_variables).to be bridge.yaml_variables
end
it 'reads options from metadata' do
expect(bridge.options).not_to be_empty
expect(bridge.metadata).to be_a Ci::BuildMetadata
expect(bridge.read_attribute(:options)).to be_nil
expect(bridge.metadata.config_options).to be bridge.options
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
...@@ -4,14 +4,25 @@ require 'spec_helper' ...@@ -4,14 +4,25 @@ 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
create(:ci_bridge, pipeline: pipeline) create(:ci_bridge, :variables, status: :created,
options: options,
pipeline: pipeline)
end
let(:options) do
{ trigger: { project: 'my/project', branch: 'master' } }
end end
it { is_expected.to include_module(Ci::PipelineDelegator) } it { is_expected.to include_module(Ci::PipelineDelegator) }
it 'has many sourced pipelines' do
expect(bridge).to have_many(:sourced_pipelines)
end
describe '#tags' do describe '#tags' do
it 'only has a bridge tag' do it 'only has a bridge tag' do
expect(bridge.tags).to eq [:bridge] expect(bridge.tags).to eq [:bridge]
...@@ -41,4 +52,222 @@ describe Ci::Bridge do ...@@ -41,4 +52,222 @@ describe Ci::Bridge do
expect(bridge.scoped_variables_hash.keys).to include(*variables) expect(bridge.scoped_variables_hash.keys).to include(*variables)
end end
end end
describe '#inherit_status_from_downstream!' do
let(:downstream_pipeline) { build(:ci_pipeline, status: downstream_status) }
before do
bridge.status = 'pending'
create(:ci_sources_pipeline, pipeline: downstream_pipeline, source_job: bridge)
end
subject { bridge.inherit_status_from_downstream!(downstream_pipeline) }
context 'when status is not supported' do
(::Ci::Pipeline::AVAILABLE_STATUSES - ::Ci::Pipeline::COMPLETED_STATUSES).map(&:to_s).each do |status|
context "when status is #{status}" do
let(:downstream_status) { status }
it 'returns false' do
expect(subject).to eq(false)
end
it 'does not change the bridge status' do
expect { subject }.not_to change { bridge.status }.from('pending')
end
end
end
end
context 'when status is supported' do
using RSpec::Parameterized::TableSyntax
where(:downstream_status, :upstream_status) do
[
%w[success success],
*::Ci::Pipeline.completed_statuses.without(:success).map { |status| [status.to_s, 'failed'] }
]
end
with_them do
it 'inherits the downstream status' do
expect { subject }.to change { bridge.status }.from('pending').to(upstream_status)
end
end
end
end
describe '#dependent?' do
subject { bridge.dependent? }
context 'when bridge has strategy depend' do
let(:options) { { trigger: { project: 'my/project', strategy: 'depend' } } }
it { is_expected.to be true }
end
context 'when bridge does not have strategy depend' do
it { is_expected.to be false }
end
end
describe '#yaml_variables' do
it 'returns YAML variables' do
expect(bridge.yaml_variables)
.to include(key: 'BRIDGE', value: 'cross', public: true)
end
end
describe '#downstream_variables' do
it 'returns variables that are going to be passed downstream' do
expect(bridge.downstream_variables)
.to include(key: 'BRIDGE', value: 'cross')
end
context 'when using variables interpolation' do
let(:yaml_variables) do
[
{
key: 'EXPANDED',
value: '$BRIDGE-bridge',
public: true
},
{
key: 'UPSTREAM_CI_PIPELINE_ID',
value: '$CI_PIPELINE_ID',
public: true
},
{
key: 'UPSTREAM_CI_PIPELINE_URL',
value: '$CI_PIPELINE_URL',
public: true
}
]
end
before do
bridge.yaml_variables.concat(yaml_variables)
end
it 'correctly expands variables with interpolation' do
expanded_values = pipeline
.persisted_variables
.to_hash
.transform_keys { |key| "UPSTREAM_#{key}" }
.map { |key, value| { key: key, value: value } }
.push(key: 'EXPANDED', value: 'cross-bridge')
expect(bridge.downstream_variables)
.to match(a_collection_including(*expanded_values))
end
end
context 'when recursive interpolation has been used' do
before do
bridge.yaml_variables << { key: 'EXPANDED', value: '$EXPANDED', public: true }
end
it 'does not expand variable recursively' do
expect(bridge.downstream_variables)
.to include(key: 'EXPANDED', value: '$EXPANDED')
end
end
end
describe 'metadata support' do
it 'reads YAML variables from metadata' do
expect(bridge.yaml_variables).not_to be_empty
expect(bridge.metadata).to be_a Ci::BuildMetadata
expect(bridge.read_attribute(:yaml_variables)).to be_nil
expect(bridge.metadata.config_variables).to be bridge.yaml_variables
end
it 'reads options from metadata' do
expect(bridge.options).not_to be_empty
expect(bridge.metadata).to be_a Ci::BuildMetadata
expect(bridge.read_attribute(:options)).to be_nil
expect(bridge.metadata.config_options).to be bridge.options
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
describe '#target_ref' do
context 'when trigger is defined' do
it 'returns a ref name' do
expect(bridge.target_ref).to eq 'master'
end
context 'when using variable expansion' do
let(:options) { { trigger: { project: 'my/project', branch: '$BRIDGE-master' } } }
it 'correctly expands variables' do
expect(bridge.target_ref).to eq('cross-master')
end
end
end
context 'when trigger does not have project defined' do
let(:options) { nil }
it 'returns nil' do
expect(bridge.target_ref).to be_nil
end
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment