Commit 103269b9 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'fix/gb/fix-pipeline-build-chain-tag-evaluation' into 'master'

Fix invalid pipeline build chain tag evaluation

Closes #40944

See merge request gitlab-org/gitlab-ce!15805
parents d32032de 99101f94
...@@ -12,7 +12,8 @@ module Ci ...@@ -12,7 +12,8 @@ module Ci
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block) def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block)
@pipeline = Ci::Pipeline.new @pipeline = Ci::Pipeline.new
command = OpenStruct.new(source: source, command = Gitlab::Ci::Pipeline::Chain::Command.new(
source: source,
origin_ref: params[:ref], origin_ref: params[:ref],
checkout_sha: params[:checkout_sha], checkout_sha: params[:checkout_sha],
after_sha: params[:after], after_sha: params[:after],
......
...@@ -3,14 +3,13 @@ module Gitlab ...@@ -3,14 +3,13 @@ module Gitlab
module Pipeline module Pipeline
module Chain module Chain
class Base class Base
attr_reader :pipeline, :project, :current_user attr_reader :pipeline, :command
delegate :project, :current_user, to: :command
def initialize(pipeline, command) def initialize(pipeline, command)
@pipeline = pipeline @pipeline = pipeline
@command = command @command = command
@project = command.project
@current_user = command.current_user
end end
def perform! def perform!
......
...@@ -3,20 +3,18 @@ module Gitlab ...@@ -3,20 +3,18 @@ module Gitlab
module Pipeline module Pipeline
module Chain module Chain
class Build < Chain::Base class Build < Chain::Base
include Chain::Helpers
def perform! def perform!
@pipeline.assign_attributes( @pipeline.assign_attributes(
source: @command.source, source: @command.source,
project: @project, project: @command.project,
ref: ref, ref: @command.ref,
sha: sha, sha: @command.sha,
before_sha: before_sha, before_sha: @command.before_sha,
tag: tag_exists?, tag: @command.tag_exists?,
trigger_requests: Array(@command.trigger_request), trigger_requests: Array(@command.trigger_request),
user: @current_user, user: @command.current_user,
pipeline_schedule: @command.schedule, pipeline_schedule: @command.schedule,
protected: protected_ref? protected: @command.protected_ref?
) )
@pipeline.set_config_source @pipeline.set_config_source
...@@ -25,32 +23,6 @@ module Gitlab ...@@ -25,32 +23,6 @@ module Gitlab
def break? def break?
false false
end end
private
def ref
@ref ||= Gitlab::Git.ref_name(origin_ref)
end
def sha
@project.commit(origin_sha || origin_ref).try(:id)
end
def origin_ref
@command.origin_ref
end
def origin_sha
@command.checkout_sha || @command.after_sha
end
def before_sha
@command.checkout_sha || @command.before_sha || Gitlab::Git::BLANK_SHA
end
def protected_ref?
@project.protected_for?(ref)
end
end end
end end
end end
......
module Gitlab
module Ci
module Pipeline
module Chain
Command = Struct.new(
:source, :project, :current_user,
:origin_ref, :checkout_sha, :after_sha, :before_sha,
:trigger_request, :schedule,
:ignore_skip_ci, :save_incompleted,
:seeds_block
) do
include Gitlab::Utils::StrongMemoize
def initialize(**params)
params.each do |key, value|
self[key] = value
end
end
def branch_exists?
strong_memoize(:is_branch) do
project.repository.branch_exists?(ref)
end
end
def tag_exists?
strong_memoize(:is_tag) do
project.repository.tag_exists?(ref)
end
end
def ref
strong_memoize(:ref) do
Gitlab::Git.ref_name(origin_ref)
end
end
def sha
strong_memoize(:sha) do
project.commit(origin_sha || origin_ref).try(:id)
end
end
def origin_sha
checkout_sha || after_sha
end
def before_sha
self[:before_sha] || checkout_sha || Gitlab::Git::BLANK_SHA
end
def protected_ref?
strong_memoize(:protected_ref) do
project.protected_for?(ref)
end
end
end
end
end
end
end
...@@ -3,18 +3,6 @@ module Gitlab ...@@ -3,18 +3,6 @@ module Gitlab
module Pipeline module Pipeline
module Chain module Chain
module Helpers module Helpers
def branch_exists?
return @is_branch if defined?(@is_branch)
@is_branch = project.repository.branch_exists?(pipeline.ref)
end
def tag_exists?
return @is_tag if defined?(@is_tag)
@is_tag = project.repository.tag_exists?(pipeline.ref)
end
def error(message) def error(message)
pipeline.errors.add(:base, message) pipeline.errors.add(:base, message)
end end
......
...@@ -14,7 +14,7 @@ module Gitlab ...@@ -14,7 +14,7 @@ module Gitlab
unless allowed_to_trigger_pipeline? unless allowed_to_trigger_pipeline?
if can?(current_user, :create_pipeline, project) if can?(current_user, :create_pipeline, project)
return error("Insufficient permissions for protected ref '#{pipeline.ref}'") return error("Insufficient permissions for protected ref '#{command.ref}'")
else else
return error('Insufficient permissions to create a new pipeline') return error('Insufficient permissions to create a new pipeline')
end end
...@@ -29,7 +29,7 @@ module Gitlab ...@@ -29,7 +29,7 @@ module Gitlab
if current_user if current_user
allowed_to_create? allowed_to_create?
else # legacy triggers don't have a corresponding user else # legacy triggers don't have a corresponding user
!project.protected_for?(@pipeline.ref) !@command.protected_ref?
end end
end end
...@@ -38,10 +38,10 @@ module Gitlab ...@@ -38,10 +38,10 @@ module Gitlab
access = Gitlab::UserAccess.new(current_user, project: project) access = Gitlab::UserAccess.new(current_user, project: project)
if branch_exists? if @command.branch_exists?
access.can_update_branch?(@pipeline.ref) access.can_update_branch?(@command.ref)
elsif tag_exists? elsif @command.tag_exists?
access.can_create_tag?(@pipeline.ref) access.can_create_tag?(@command.ref)
else else
true # Allow it for now and we'll reject when we check ref existence true # Allow it for now and we'll reject when we check ref existence
end end
......
...@@ -7,14 +7,11 @@ module Gitlab ...@@ -7,14 +7,11 @@ module Gitlab
include Chain::Helpers include Chain::Helpers
def perform! def perform!
unless branch_exists? || tag_exists? unless @command.branch_exists? || @command.tag_exists?
return error('Reference not found') return error('Reference not found')
end end
## TODO, we check commit in the service, that is why unless @command.sha
# there is no repository access here.
#
unless pipeline.sha
return error('Commit not found') return error('Commit not found')
end end
end end
......
...@@ -6,7 +6,8 @@ describe Gitlab::Ci::Pipeline::Chain::Build do ...@@ -6,7 +6,8 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
let(:pipeline) { Ci::Pipeline.new } let(:pipeline) { Ci::Pipeline.new }
let(:command) do let(:command) do
double('command', source: :push, Gitlab::Ci::Pipeline::Chain::Command.new(
source: :push,
origin_ref: 'master', origin_ref: 'master',
checkout_sha: project.commit.id, checkout_sha: project.commit.id,
after_sha: nil, after_sha: nil,
...@@ -21,31 +22,65 @@ describe Gitlab::Ci::Pipeline::Chain::Build do ...@@ -21,31 +22,65 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
before do before do
stub_repository_ci_yaml_file(sha: anything) stub_repository_ci_yaml_file(sha: anything)
step.perform!
end end
it 'never breaks the chain' do it 'never breaks the chain' do
step.perform!
expect(step.break?).to be false expect(step.break?).to be false
end end
it 'fills pipeline object with data' do it 'fills pipeline object with data' do
step.perform!
expect(pipeline.sha).not_to be_empty expect(pipeline.sha).not_to be_empty
expect(pipeline.sha).to eq project.commit.id expect(pipeline.sha).to eq project.commit.id
expect(pipeline.ref).to eq 'master' expect(pipeline.ref).to eq 'master'
expect(pipeline.tag).to be false
expect(pipeline.user).to eq user expect(pipeline.user).to eq user
expect(pipeline.project).to eq project expect(pipeline.project).to eq project
end end
it 'sets a valid config source' do it 'sets a valid config source' do
step.perform!
expect(pipeline.repository_source?).to be true expect(pipeline.repository_source?).to be true
end end
it 'returns a valid pipeline' do it 'returns a valid pipeline' do
step.perform!
expect(pipeline).to be_valid expect(pipeline).to be_valid
end end
it 'does not persist a pipeline' do it 'does not persist a pipeline' do
step.perform!
expect(pipeline).not_to be_persisted expect(pipeline).not_to be_persisted
end end
context 'when pipeline is running for a tag' do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
source: :push,
origin_ref: 'mytag',
checkout_sha: project.commit.id,
after_sha: nil,
before_sha: nil,
trigger_request: nil,
schedule: nil,
project: project,
current_user: user)
end
before do
allow_any_instance_of(Repository).to receive(:tag_exists?).with('mytag').and_return(true)
step.perform!
end
it 'correctly indicated that this is a tagged pipeline' do
expect(pipeline).to be_tag
end
end
end end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Command do
set(:project) { create(:project, :repository) }
describe '#initialize' do
subject do
described_class.new(origin_ref: 'master')
end
it 'properly initialises object from hash' do
expect(subject.origin_ref).to eq('master')
end
end
context 'handling of origin_ref' do
let(:command) { described_class.new(project: project, origin_ref: origin_ref) }
describe '#branch_exists?' do
subject { command.branch_exists? }
context 'for existing branch' do
let(:origin_ref) { 'master' }
it { is_expected.to eq(true) }
end
context 'for invalid branch' do
let(:origin_ref) { 'something' }
it { is_expected.to eq(false) }
end
end
describe '#tag_exists?' do
subject { command.tag_exists? }
context 'for existing ref' do
let(:origin_ref) { 'v1.0.0' }
it { is_expected.to eq(true) }
end
context 'for invalid ref' do
let(:origin_ref) { 'something' }
it { is_expected.to eq(false) }
end
end
describe '#ref' do
subject { command.ref }
context 'for regular ref' do
let(:origin_ref) { 'master' }
it { is_expected.to eq('master') }
end
context 'for branch ref' do
let(:origin_ref) { 'refs/heads/master' }
it { is_expected.to eq('master') }
end
context 'for tag ref' do
let(:origin_ref) { 'refs/tags/1.0.0' }
it { is_expected.to eq('1.0.0') }
end
context 'for other refs' do
let(:origin_ref) { 'refs/merge-requests/11/head' }
it { is_expected.to eq('refs/merge-requests/11/head') }
end
end
end
describe '#sha' do
subject { command.sha }
context 'when invalid checkout_sha is specified' do
let(:command) { described_class.new(project: project, checkout_sha: 'aaa') }
it 'returns empty value' do
is_expected.to be_nil
end
end
context 'when a valid checkout_sha is specified' do
let(:command) { described_class.new(project: project, checkout_sha: project.commit.id) }
it 'returns checkout_sha' do
is_expected.to eq(project.commit.id)
end
end
context 'when a valid after_sha is specified' do
let(:command) { described_class.new(project: project, after_sha: project.commit.id) }
it 'returns after_sha' do
is_expected.to eq(project.commit.id)
end
end
context 'when a valid origin_ref is specified' do
let(:command) { described_class.new(project: project, origin_ref: 'HEAD') }
it 'returns SHA for given ref' do
is_expected.to eq(project.commit.id)
end
end
end
describe '#origin_sha' do
subject { command.origin_sha }
context 'when using checkout_sha and after_sha' do
let(:command) { described_class.new(project: project, checkout_sha: 'aaa', after_sha: 'bbb') }
it 'uses checkout_sha' do
is_expected.to eq('aaa')
end
end
context 'when using after_sha only' do
let(:command) { described_class.new(project: project, after_sha: 'bbb') }
it 'uses after_sha' do
is_expected.to eq('bbb')
end
end
end
describe '#before_sha' do
subject { command.before_sha }
context 'when using checkout_sha and before_sha' do
let(:command) { described_class.new(project: project, checkout_sha: 'aaa', before_sha: 'bbb') }
it 'uses before_sha' do
is_expected.to eq('bbb')
end
end
context 'when using checkout_sha only' do
let(:command) { described_class.new(project: project, checkout_sha: 'aaa') }
it 'uses checkout_sha' do
is_expected.to eq('aaa')
end
end
context 'when checkout_sha and before_sha are empty' do
let(:command) { described_class.new(project: project) }
it 'uses BLANK_SHA' do
is_expected.to eq(Gitlab::Git::BLANK_SHA)
end
end
end
describe '#protected_ref?' do
let(:command) { described_class.new(project: project, origin_ref: 'my-branch') }
subject { command.protected_ref? }
context 'when a ref is protected' do
before do
expect_any_instance_of(Project).to receive(:protected_for?).with('my-branch').and_return(true)
end
it { is_expected.to eq(true) }
end
context 'when a ref is unprotected' do
before do
expect_any_instance_of(Project).to receive(:protected_for?).with('my-branch').and_return(false)
end
it { is_expected.to eq(false) }
end
end
end
...@@ -10,9 +10,9 @@ describe Gitlab::Ci::Pipeline::Chain::Create do ...@@ -10,9 +10,9 @@ describe Gitlab::Ci::Pipeline::Chain::Create do
end end
let(:command) do let(:command) do
double('command', project: project, Gitlab::Ci::Pipeline::Chain::Command.new(
current_user: user, project: project,
seeds_block: nil) current_user: user, seeds_block: nil)
end end
let(:step) { described_class.new(pipeline, command) } let(:step) { described_class.new(pipeline, command) }
......
...@@ -5,7 +5,7 @@ describe Gitlab::Ci::Pipeline::Chain::Sequence do ...@@ -5,7 +5,7 @@ describe Gitlab::Ci::Pipeline::Chain::Sequence do
set(:user) { create(:user) } set(:user) { create(:user) }
let(:pipeline) { build_stubbed(:ci_pipeline) } let(:pipeline) { build_stubbed(:ci_pipeline) }
let(:command) { double('command' ) } let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new }
let(:first_step) { spy('first step') } let(:first_step) { spy('first step') }
let(:second_step) { spy('second step') } let(:second_step) { spy('second step') }
let(:sequence) { [first_step, second_step] } let(:sequence) { [first_step, second_step] }
......
...@@ -6,7 +6,8 @@ describe Gitlab::Ci::Pipeline::Chain::Skip do ...@@ -6,7 +6,8 @@ describe Gitlab::Ci::Pipeline::Chain::Skip do
set(:pipeline) { create(:ci_pipeline, project: project) } set(:pipeline) { create(:ci_pipeline, project: project) }
let(:command) do let(:command) do
double('command', project: project, Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
current_user: user, current_user: user,
ignore_skip_ci: false, ignore_skip_ci: false,
save_incompleted: true) save_incompleted: true)
......
...@@ -5,11 +5,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do ...@@ -5,11 +5,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
set(:user) { create(:user) } set(:user) { create(:user) }
let(:pipeline) do let(:pipeline) do
build_stubbed(:ci_pipeline, ref: ref, project: project) build_stubbed(:ci_pipeline, project: project)
end end
let(:command) do let(:command) do
double('command', project: project, current_user: user) Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, origin_ref: ref)
end end
let(:step) { described_class.new(pipeline, command) } let(:step) { described_class.new(pipeline, command) }
......
...@@ -5,7 +5,8 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do ...@@ -5,7 +5,8 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
set(:user) { create(:user) } set(:user) { create(:user) }
let(:command) do let(:command) do
double('command', project: project, Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
current_user: user, current_user: user,
save_incompleted: true) save_incompleted: true)
end end
......
...@@ -3,10 +3,7 @@ require 'spec_helper' ...@@ -3,10 +3,7 @@ require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
set(:project) { create(:project, :repository) } set(:project) { create(:project, :repository) }
set(:user) { create(:user) } set(:user) { create(:user) }
let(:pipeline) { build_stubbed(:ci_pipeline) }
let(:command) do
double('command', project: project, current_user: user)
end
let!(:step) { described_class.new(pipeline, command) } let!(:step) { described_class.new(pipeline, command) }
...@@ -14,9 +11,10 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do ...@@ -14,9 +11,10 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
step.perform! step.perform!
end end
context 'when pipeline ref and sha exists' do context 'when ref and sha exists' do
let(:pipeline) do let(:command) do
build_stubbed(:ci_pipeline, ref: 'master', sha: '123', project: project) Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, origin_ref: 'master', checkout_sha: project.commit.id)
end end
it 'does not break the chain' do it 'does not break the chain' do
...@@ -28,9 +26,10 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do ...@@ -28,9 +26,10 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
end end
end end
context 'when pipeline ref does not exist' do context 'when ref does not exist' do
let(:pipeline) do let(:command) do
build_stubbed(:ci_pipeline, ref: 'something', project: project) Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, origin_ref: 'something')
end end
it 'breaks the chain' do it 'breaks the chain' do
...@@ -43,9 +42,10 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do ...@@ -43,9 +42,10 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
end end
end end
context 'when pipeline does not have SHA set' do context 'when does not have existing SHA set' do
let(:pipeline) do let(:command) do
build_stubbed(:ci_pipeline, ref: 'master', sha: nil, project: project) Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, origin_ref: 'master', checkout_sha: 'something')
end end
it 'breaks the chain' do it 'breaks the chain' do
......
...@@ -518,5 +518,20 @@ describe Ci::CreatePipelineService do ...@@ -518,5 +518,20 @@ describe Ci::CreatePipelineService do
end end
end end
end end
context 'when pipeline is running for a tag' do
before do
config = YAML.dump(test: { script: 'test', only: ['branches'] },
deploy: { script: 'deploy', only: ['tags'] })
stub_ci_pipeline_yaml_file(config)
end
it 'creates a tagged pipeline' do
pipeline = execute_service(ref: 'v1.0.0')
expect(pipeline.tag?).to be true
end
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