Commit 8f47d484 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Extract pipeline chain builder classes from service

parent 1209f4f6
...@@ -2,6 +2,9 @@ module Ci ...@@ -2,6 +2,9 @@ module Ci
class CreatePipelineService < BaseService class CreatePipelineService < BaseService
attr_reader :pipeline attr_reader :pipeline
SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Validate,
Gitlab::Ci::Pipeline::Chain::Skip]
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil) def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil)
@pipeline = Ci::Pipeline.new( @pipeline = Ci::Pipeline.new(
source: source, source: source,
...@@ -16,11 +19,23 @@ module Ci ...@@ -16,11 +19,23 @@ module Ci
protected: project.protected_for?(ref) protected: project.protected_for?(ref)
) )
result = validate_project_and_git_items || command = OpenStruct.new(ignore_skip_ci: ignore_skip_ci,
validate_pipeline(ignore_skip_ci: ignore_skip_ci, save_incompleted: save_on_errors,
save_on_errors: save_on_errors) trigger_request: trigger_request,
schedule: schedule,
project: project,
current_user: current_user)
sequence = SEQUENCE.map { |chain| chain.new(pipeline, command) }
done = sequence.any? do |chain|
chain.perform!
chain.break?
end
return result if result update_merge_requests_head_pipeline if pipeline.persisted?
return pipeline if done
begin begin
Ci::Pipeline.transaction do Ci::Pipeline.transaction do
...@@ -36,10 +51,8 @@ module Ci ...@@ -36,10 +51,8 @@ module Ci
return error("Failed to persist the pipeline: #{e}") return error("Failed to persist the pipeline: #{e}")
end end
update_merge_requests_head_pipeline update_merge_requests_head_pipeline if pipeline.persisted?
cancel_pending_pipelines if project.auto_cancel_pending_pipelines? cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
pipeline_created_counter.increment(source: source) pipeline_created_counter.increment(source: source)
pipeline.tap(&:process!) pipeline.tap(&:process!)
...@@ -47,65 +60,12 @@ module Ci ...@@ -47,65 +60,12 @@ module Ci
private private
def validate_project_and_git_items def commit
unless project.builds_enabled? @commit ||= project.commit(origin_sha || origin_ref)
return error('Pipeline is disabled')
end
unless allowed_to_trigger_pipeline?
if can?(current_user, :create_pipeline, project)
return error("Insufficient permissions for protected ref '#{ref}'")
else
return error('Insufficient permissions to create a new pipeline')
end
end
unless branch? || tag?
return error('Reference not found')
end
unless commit
return error('Commit not found')
end
end
def validate_pipeline(ignore_skip_ci:, save_on_errors:)
unless pipeline.config_processor
unless pipeline.ci_yaml_file
return error("Missing #{pipeline.ci_yaml_file_path} file")
end
return error(pipeline.yaml_errors, save: save_on_errors)
end
if !ignore_skip_ci && skip_ci?
pipeline.skip if save_on_errors
return pipeline
end
unless pipeline.has_stage_seeds?
return error('No stages / jobs for this pipeline.')
end
end
def allowed_to_trigger_pipeline?
if current_user
allowed_to_create?
else # legacy triggers don't have a corresponding user
!project.protected_for?(ref)
end
end end
def allowed_to_create? def sha
return unless can?(current_user, :create_pipeline, project) commit.try(:id)
access = Gitlab::UserAccess.new(current_user, project: project)
if branch?
access.can_update_branch?(ref)
elsif tag?
access.can_create_tag?(ref)
else
true # Allow it for now and we'll reject when we check ref existence
end
end end
def update_merge_requests_head_pipeline def update_merge_requests_head_pipeline
...@@ -115,11 +75,6 @@ module Ci ...@@ -115,11 +75,6 @@ module Ci
.update_all(head_pipeline_id: @pipeline.id) .update_all(head_pipeline_id: @pipeline.id)
end end
def skip_ci?
return false unless pipeline.git_commit_message
pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i
end
def cancel_pending_pipelines def cancel_pending_pipelines
Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables| Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables|
cancelables.find_each do |cancelable| cancelables.find_each do |cancelable|
...@@ -136,13 +91,6 @@ module Ci ...@@ -136,13 +91,6 @@ module Ci
.created_or_pending .created_or_pending
end end
def commit
@commit ||= project.commit(origin_sha || origin_ref)
end
def sha
commit.try(:id)
end
def before_sha def before_sha
params[:checkout_sha] || params[:before] || Gitlab::Git::BLANK_SHA params[:checkout_sha] || params[:before] || Gitlab::Git::BLANK_SHA
...@@ -156,13 +104,6 @@ module Ci ...@@ -156,13 +104,6 @@ module Ci
params[:ref] params[:ref]
end end
def branch?
return @is_branch if defined?(@is_branch)
@is_branch =
project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
end
def tag? def tag?
return @is_tag if defined?(@is_tag) return @is_tag if defined?(@is_tag)
...@@ -178,17 +119,6 @@ module Ci ...@@ -178,17 +119,6 @@ module Ci
origin_sha && origin_sha != Gitlab::Git::BLANK_SHA origin_sha && origin_sha != Gitlab::Git::BLANK_SHA
end end
def error(message, save: false)
pipeline.tap do
pipeline.errors.add(:base, message)
if save
pipeline.drop
update_merge_requests_head_pipeline
end
end
end
def pipeline_created_counter def pipeline_created_counter
@pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_total, "Counter of pipelines created") @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_total, "Counter of pipelines created")
end end
......
module Gitlab
module Ci
module Pipeline
module Chain
class Base
attr_reader :pipeline, :project, :current_user
def initialize(pipeline, command)
@pipeline = pipeline
@command = command
@project = command.project
@current_user = command.current_user
end
def perform!
raise NotImplementedError
end
def break?
raise NotImplementedError
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Chain
class Skip < Chain::Base
SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i
def perform!
if skipped?
@pipeline.skip if @command.save_incompleted
end
end
def skipped?
!@command.ignore_skip_ci && commit_message_skips_ci?
end
def break?
skipped?
end
private
def commit_message_skips_ci?
return false unless @pipeline.git_commit_message
@skipped ||= @pipeline.git_commit_message =~ SKIP_PATTERN
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Chain
class Validate < Chain::Base
include Gitlab::Allowable
def perform!
validate_project! || validate_repository! || validate_pipeline!
end
def break?
@pipeline.errors.any? || @pipeline.persisted?
end
def allowed_to_trigger_pipeline?
if current_user
allowed_to_create?
else # legacy triggers don't have a corresponding user
!project.protected_for?(@pipeline.ref)
end
end
def allowed_to_create?
return unless can?(current_user, :create_pipeline, project)
access = Gitlab::UserAccess.new(current_user, project: project)
if branch?
access.can_update_branch?(@pipeline.ref)
elsif tag?
access.can_create_tag?(@pipeline.ref)
else
true # Allow it for now and we'll reject when we check ref existence
end
end
private
def validate_project!
unless project.builds_enabled?
return error('Pipeline is disabled')
end
unless allowed_to_trigger_pipeline?
if can?(current_user, :create_pipeline, project)
return error("Insufficient permissions for protected ref '#{pipeline.ref}'")
else
return error('Insufficient permissions to create a new pipeline')
end
end
end
def validate_repository!
unless branch? || tag?
return error('Reference not found')
end
unless pipeline.sha
return error('Commit not found')
end
end
def validate_pipeline!
unless @pipeline.config_processor
unless @pipeline.ci_yaml_file
return error("Missing #{@pipeline.ci_yaml_file_path} file")
end
if @command.save_incompleted && @pipeline.has_yaml_errors?
@pipeline.drop
end
return error(@pipeline.yaml_errors)
end
unless @pipeline.has_stage_seeds?
return error('No stages / jobs for this pipeline.')
end
end
## TODO, move to Pipeline as `branch_exists?`
#
def branch?
return @is_branch if defined?(@is_branch)
@is_branch = project.repository
.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref)
end
## TODO, move to pipeline as `tag_exists?`
#
def tag?
return @is_tag if defined?(@is_tag)
@is_tag = project.repository
.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + pipeline.ref)
end
def error(message)
pipeline.errors.add(:base, message)
end
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate do
describe '#allowed_to_create?' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:ref) { 'master' }
let(:pipeline) do
build_stubbed(:ci_pipeline, ref: ref, project: project)
end
let(:command) do
double('command', project: project, current_user: user)
end
subject do
described_class.new(pipeline, command).allowed_to_create?
end
context 'when user is a developer' do
before do
project.add_developer(user)
end
it { is_expected.to be_truthy }
context 'when the branch is protected' do
let!(:protected_branch) do
create(:protected_branch, project: project, name: ref)
end
it { is_expected.to be_falsey }
context 'when developers are allowed to merge' do
let!(:protected_branch) do
create(:protected_branch,
:developers_can_merge,
project: project,
name: ref)
end
it { is_expected.to be_truthy }
end
end
context 'when the tag is protected' do
let(:ref) { 'v1.0.0' }
let!(:protected_tag) do
create(:protected_tag, project: project, name: ref)
end
it { is_expected.to be_falsey }
context 'when developers are allowed to create the tag' do
let!(:protected_tag) do
create(:protected_tag,
:developers_can_create,
project: project,
name: ref)
end
it { is_expected.to be_truthy }
end
end
end
context 'when user is a master' do
before do
project.add_master(user)
end
it { is_expected.to be_truthy }
context 'when the branch is protected' do
let!(:protected_branch) do
create(:protected_branch, project: project, name: ref)
end
it { is_expected.to be_truthy }
end
context 'when the tag is protected' do
let(:ref) { 'v1.0.0' }
let!(:protected_tag) do
create(:protected_tag, project: project, name: ref)
end
it { is_expected.to be_truthy }
context 'when no one can create the tag' do
let!(:protected_tag) do
create(:protected_tag,
:no_one_can_create,
project: project,
name: ref)
end
it { is_expected.to be_falsey }
end
end
end
context 'when owner cannot create pipeline' do
it { is_expected.to be_falsey }
end
end
end
...@@ -482,103 +482,4 @@ describe Ci::CreatePipelineService do ...@@ -482,103 +482,4 @@ describe Ci::CreatePipelineService do
end end
end end
describe '#allowed_to_create?' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:ref) { 'master' }
subject do
described_class.new(project, user, ref: ref)
.send(:allowed_to_create?)
end
context 'when user is a developer' do
before do
project.add_developer(user)
end
it { is_expected.to be_truthy }
context 'when the branch is protected' do
let!(:protected_branch) do
create(:protected_branch, project: project, name: ref)
end
it { is_expected.to be_falsey }
context 'when developers are allowed to merge' do
let!(:protected_branch) do
create(:protected_branch,
:developers_can_merge,
project: project,
name: ref)
end
it { is_expected.to be_truthy }
end
end
context 'when the tag is protected' do
let(:ref) { 'v1.0.0' }
let!(:protected_tag) do
create(:protected_tag, project: project, name: ref)
end
it { is_expected.to be_falsey }
context 'when developers are allowed to create the tag' do
let!(:protected_tag) do
create(:protected_tag,
:developers_can_create,
project: project,
name: ref)
end
it { is_expected.to be_truthy }
end
end
end
context 'when user is a master' do
before do
project.add_master(user)
end
it { is_expected.to be_truthy }
context 'when the branch is protected' do
let!(:protected_branch) do
create(:protected_branch, project: project, name: ref)
end
it { is_expected.to be_truthy }
end
context 'when the tag is protected' do
let(:ref) { 'v1.0.0' }
let!(:protected_tag) do
create(:protected_tag, project: project, name: ref)
end
it { is_expected.to be_truthy }
context 'when no one can create the tag' do
let!(:protected_tag) do
create(:protected_tag,
:no_one_can_create,
project: project,
name: ref)
end
it { is_expected.to be_falsey }
end
end
end
context 'when owner cannot create pipeline' do
it { is_expected.to be_falsey }
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