Commit c60bdf91 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'backstage/gb/jobs-triggering-policy-specifications' into 'master'

Implement job policy specifications

Closes #37280

See merge request gitlab-org/gitlab-ce!14265
parents 7e69f188 14966419
......@@ -31,6 +31,7 @@ module Ci
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
delegate :id, to: :project, prefix: true
delegate :full_path, to: :project, prefix: true
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
validates :sha, presence: { unless: :importing? }
......@@ -336,7 +337,7 @@ module Ci
return @config_processor if defined?(@config_processor)
@config_processor ||= begin
Gitlab::Ci::YamlProcessor.new(ci_yaml_file, project.full_path)
Gitlab::Ci::YamlProcessor.new(ci_yaml_file)
rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e
self.yaml_errors = e.message
nil
......
module Gitlab
module Ci
module Build
module Policy
def self.fabricate(specs)
specifications = specs.to_h.map do |spec, value|
self.const_get(spec.to_s.camelize).new(value)
end
specifications.compact
end
end
end
end
end
module Gitlab
module Ci
module Build
module Policy
class Kubernetes < Policy::Specification
def initialize(spec)
unless spec.to_sym == :active
raise UnknownPolicyError
end
end
def satisfied_by?(pipeline)
pipeline.has_kubernetes_active?
end
end
end
end
end
end
module Gitlab
module Ci
module Build
module Policy
class Refs < Policy::Specification
def initialize(refs)
@patterns = Array(refs)
end
def satisfied_by?(pipeline)
@patterns.any? do |pattern|
pattern, path = pattern.split('@', 2)
matches_path?(path, pipeline) &&
matches_pattern?(pattern, pipeline)
end
end
private
def matches_path?(path, pipeline)
return true unless path
pipeline.project_full_path == path
end
def matches_pattern?(pattern, pipeline)
return true if pipeline.tag? && pattern == 'tags'
return true if pipeline.branch? && pattern == 'branches'
return true if pipeline.source == pattern
return true if pipeline.source&.pluralize == pattern
if pattern.first == "/" && pattern.last == "/"
Regexp.new(pattern[1...-1]) =~ pipeline.ref
else
pattern == pipeline.ref
end
end
end
end
end
end
end
module Gitlab
module Ci
module Build
module Policy
##
# Abstract class that defines an interface of job policy
# specification.
#
# Used for job's only/except policy configuration.
#
class Specification
UnknownPolicyError = Class.new(StandardError)
def initialize(spec)
@spec = spec
end
def satisfied_by?(pipeline)
raise NotImplementedError
end
end
end
end
end
end
......@@ -5,12 +5,11 @@ module Gitlab
include Gitlab::Ci::Config::Entry::LegacyValidationHelpers
attr_reader :path, :cache, :stages, :jobs
attr_reader :cache, :stages, :jobs
def initialize(config, path = nil)
def initialize(config)
@ci_config = Gitlab::Ci::Config.new(config)
@config = @ci_config.to_hash
@path = path
unless @ci_config.valid?
raise ValidationError, @ci_config.errors.first
......@@ -21,28 +20,12 @@ module Gitlab
raise ValidationError, e.message
end
def builds_for_stage_and_ref(stage, ref, tag = false, source = nil)
jobs_for_stage_and_ref(stage, ref, tag, source).map do |name, _|
build_attributes(name)
end
end
def builds
@jobs.map do |name, _|
build_attributes(name)
end
end
def stage_seeds(pipeline)
seeds = @stages.uniq.map do |stage|
builds = pipeline_stage_builds(stage, pipeline)
Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any?
end
seeds.compact
end
def build_attributes(name)
job = @jobs[name.to_sym] || {}
......@@ -70,6 +53,32 @@ module Gitlab
}.compact }
end
def pipeline_stage_builds(stage, pipeline)
selected_jobs = @jobs.select do |_, job|
next unless job[:stage] == stage
only_specs = Gitlab::Ci::Build::Policy
.fabricate(job.fetch(:only, {}))
except_specs = Gitlab::Ci::Build::Policy
.fabricate(job.fetch(:except, {}))
only_specs.all? { |spec| spec.satisfied_by?(pipeline) } &&
except_specs.none? { |spec| spec.satisfied_by?(pipeline) }
end
selected_jobs.map { |_, job| build_attributes(job[:name]) }
end
def stage_seeds(pipeline)
seeds = @stages.uniq.map do |stage|
builds = pipeline_stage_builds(stage, pipeline)
Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any?
end
seeds.compact
end
def self.validation_message(content)
return 'Please provide content of .gitlab-ci.yml' if content.blank?
......@@ -83,34 +92,6 @@ module Gitlab
private
def pipeline_stage_builds(stage, pipeline)
builds = builds_for_stage_and_ref(
stage, pipeline.ref, pipeline.tag?, pipeline.source)
builds.select do |build|
job = @jobs[build.fetch(:name).to_sym]
has_kubernetes = pipeline.has_kubernetes_active?
only_kubernetes = job.dig(:only, :kubernetes)
except_kubernetes = job.dig(:except, :kubernetes)
[!only_kubernetes && !except_kubernetes,
only_kubernetes && has_kubernetes,
except_kubernetes && !has_kubernetes].any?
end
end
def jobs_for_ref(ref, tag = false, source = nil)
@jobs.select do |_, job|
process?(job.dig(:only, :refs), job.dig(:except, :refs), ref, tag, source)
end
end
def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil)
jobs_for_ref(ref, tag, source).select do |_, job|
job[:stage] == stage
end
end
def initial_parsing
##
# Global config
......@@ -203,51 +184,6 @@ module Gitlab
raise ValidationError, "#{name} job: on_stop job #{on_stop} needs to have action stop defined"
end
end
def process?(only_params, except_params, ref, tag, source)
if only_params.present?
return false unless matching?(only_params, ref, tag, source)
end
if except_params.present?
return false if matching?(except_params, ref, tag, source)
end
true
end
def matching?(patterns, ref, tag, source)
patterns.any? do |pattern|
pattern, path = pattern.split('@', 2)
matches_path?(path) && matches_pattern?(pattern, ref, tag, source)
end
end
def matches_path?(path)
return true unless path
path == self.path
end
def matches_pattern?(pattern, ref, tag, source)
return true if tag && pattern == 'tags'
return true if !tag && pattern == 'branches'
return true if source_to_pattern(source) == pattern
if pattern.first == "/" && pattern.last == "/"
Regexp.new(pattern[1...-1]) =~ ref
else
pattern == ref
end
end
def source_to_pattern(source)
if %w[api external web].include?(source)
source
else
source&.pluralize
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Kubernetes do
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when kubernetes service is active' do
set(:project) { create(:kubernetes_project) }
it 'is satisfied by a kubernetes pipeline' do
expect(described_class.new('active'))
.to be_satisfied_by(pipeline)
end
end
context 'when kubernetes service is inactive' do
set(:project) { create(:project) }
it 'is not satisfied by a pipeline without kubernetes available' do
expect(described_class.new('active'))
.not_to be_satisfied_by(pipeline)
end
end
context 'when kubernetes policy is invalid' do
it 'raises an error' do
expect { described_class.new('unknown') }
.to raise_error(described_class::UnknownPolicyError)
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Refs do
describe '#satisfied_by?' do
context 'when matching ref' do
let(:pipeline) { build_stubbed(:ci_pipeline, ref: 'master') }
it 'is satisfied when pipeline branch matches' do
expect(described_class.new(%w[master deploy]))
.to be_satisfied_by(pipeline)
end
it 'is not satisfied when pipeline branch does not match' do
expect(described_class.new(%w[feature fix]))
.not_to be_satisfied_by(pipeline)
end
end
context 'when maching tags' do
context 'when pipeline runs for a tag' do
let(:pipeline) do
build_stubbed(:ci_pipeline, ref: 'feature', tag: true)
end
it 'is satisfied when tags matcher is specified' do
expect(described_class.new(%w[master tags]))
.to be_satisfied_by(pipeline)
end
end
context 'when pipeline is not created for a tag' do
let(:pipeline) do
build_stubbed(:ci_pipeline, ref: 'feature', tag: false)
end
it 'is not satisfied when tag match is specified' do
expect(described_class.new(%w[master tags]))
.not_to be_satisfied_by(pipeline)
end
end
end
context 'when also matching a path' do
let(:pipeline) do
build_stubbed(:ci_pipeline, ref: 'master')
end
it 'is satisfied when provided patch matches specified one' do
expect(described_class.new(%W[master@#{pipeline.project_full_path}]))
.to be_satisfied_by(pipeline)
end
it 'is not satisfied when path differs' do
expect(described_class.new(%w[master@some/fork/repository]))
.not_to be_satisfied_by(pipeline)
end
end
context 'when maching a source' do
let(:pipeline) { build_stubbed(:ci_pipeline, source: :push) }
it 'is satisifed when provided source keyword matches' do
expect(described_class.new(%w[pushes]))
.to be_satisfied_by(pipeline)
end
it 'is not satisfied when provided source keyword does not match' do
expect(described_class.new(%w[triggers]))
.not_to be_satisfied_by(pipeline)
end
end
context 'when matching a ref by a regular expression' do
let(:pipeline) { build_stubbed(:ci_pipeline, ref: 'docs-something') }
it 'is satisfied when regexp matches pipeline ref' do
expect(described_class.new(['/docs-.*/']))
.to be_satisfied_by(pipeline)
end
it 'is not satisfied when regexp does not match pipeline ref' do
expect(described_class.new(['/fix-.*/']))
.not_to be_satisfied_by(pipeline)
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Build::Policy do
let(:policy) { spy('policy specification') }
before do
stub_const("#{described_class}::Something", policy)
end
describe '.fabricate' do
context 'when policy exists' do
it 'fabricates and initializes relevant policy' do
specs = described_class.fabricate(something: 'some value')
expect(specs).to be_an Array
expect(specs).to be_one
expect(policy).to have_received(:new).with('some value')
end
end
context 'when some policies are not defined' do
it 'gracefully skips unknown policies' do
expect { described_class.fabricate(unknown: 'first') }
.to raise_error(NameError)
end
end
context 'when passing a nil value as specs' do
it 'returns an empty array' do
specs = described_class.fabricate(nil)
expect(specs).to be_an Array
expect(specs).to be_empty
end
end
end
end
......@@ -3,8 +3,7 @@ require 'spec_helper'
module Gitlab
module Ci
describe YamlProcessor, :lib do
subject { described_class.new(config, path) }
let(:path) { 'path' }
subject { described_class.new(config) }
describe 'our current .gitlab-ci.yml' do
let(:config) { File.read("#{Rails.root}/.gitlab-ci.yml") }
......@@ -17,7 +16,7 @@ module Gitlab
end
describe '#build_attributes' do
subject { described_class.new(config, path).build_attributes(:rspec) }
subject { described_class.new(config).build_attributes(:rspec) }
describe 'coverage entry' do
describe 'code coverage regexp' do
......@@ -167,8 +166,6 @@ module Gitlab
end
context 'when kubernetes policy is specified' do
let(:pipeline) { create(:ci_empty_pipeline) }
let(:config) do
YAML.dump(
spinach: { stage: 'test', script: 'spinach' },
......@@ -204,7 +201,7 @@ module Gitlab
end
end
describe "#builds_for_stage_and_ref" do
describe "#pipeline_stage_builds" do
let(:type) { 'test' }
it "returns builds if no branch specified" do
......@@ -213,10 +210,10 @@ module Gitlab
rspec: { script: "rspec" }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).first).to eq({
stage: "test",
stage_idx: 1,
name: "rspec",
......@@ -241,9 +238,9 @@ module Gitlab
rspec: { script: "rspec", only: ["deploy"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0)
end
it "does not return builds if only has regexp with another branch" do
......@@ -252,9 +249,9 @@ module Gitlab
rspec: { script: "rspec", only: ["/^deploy$/"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0)
end
it "returns builds if only has specified this branch" do
......@@ -263,9 +260,9 @@ module Gitlab
rspec: { script: "rspec", only: ["master"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1)
end
it "returns builds if only has a list of branches including specified" do
......@@ -274,9 +271,9 @@ module Gitlab
rspec: { script: "rspec", type: type, only: %w(master deploy) }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1)
end
it "returns builds if only has a branches keyword specified" do
......@@ -285,9 +282,9 @@ module Gitlab
rspec: { script: "rspec", type: type, only: ["branches"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1)
end
it "does not return builds if only has a tags keyword" do
......@@ -296,9 +293,9 @@ module Gitlab
rspec: { script: "rspec", type: type, only: ["tags"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0)
end
it "returns builds if only has special keywords specified and source matches" do
......@@ -315,9 +312,9 @@ module Gitlab
rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(1)
end
end
......@@ -335,21 +332,27 @@ module Gitlab
rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(0)
end
end
it "returns builds if only has current repository path" do
seed_pipeline = pipeline(ref: 'deploy')
config = YAML.dump({
before_script: ["pwd"],
rspec: { script: "rspec", type: type, only: ["branches@path"] }
rspec: {
script: "rspec",
type: type,
only: ["branches@#{seed_pipeline.project_full_path}"]
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, seed_pipeline).size).to eq(1)
end
it "does not return builds if only has different repository path" do
......@@ -358,9 +361,9 @@ module Gitlab
rspec: { script: "rspec", type: type, only: ["branches@fork"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0)
end
it "returns build only for specified type" do
......@@ -371,11 +374,11 @@ module Gitlab
production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, 'fork')
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
expect(config_processor.builds_for_stage_and_ref("test", "deploy").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(1)
expect(config_processor.pipeline_stage_builds("deploy", pipeline(ref: "deploy")).size).to eq(2)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "deploy")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("deploy", pipeline(ref: "master")).size).to eq(1)
end
context 'for invalid value' do
......@@ -418,9 +421,9 @@ module Gitlab
rspec: { script: "rspec", except: ["deploy"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1)
end
it "returns builds if except has regexp with another branch" do
......@@ -429,9 +432,9 @@ module Gitlab
rspec: { script: "rspec", except: ["/^deploy$/"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1)
end
it "does not return builds if except has specified this branch" do
......@@ -440,9 +443,9 @@ module Gitlab
rspec: { script: "rspec", except: ["master"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0)
end
it "does not return builds if except has a list of branches including specified" do
......@@ -451,9 +454,9 @@ module Gitlab
rspec: { script: "rspec", type: type, except: %w(master deploy) }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0)
end
it "does not return builds if except has a branches keyword specified" do
......@@ -462,9 +465,9 @@ module Gitlab
rspec: { script: "rspec", type: type, except: ["branches"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0)
end
it "returns builds if except has a tags keyword" do
......@@ -473,9 +476,9 @@ module Gitlab
rspec: { script: "rspec", type: type, except: ["tags"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1)
end
it "does not return builds if except has special keywords specified and source matches" do
......@@ -492,9 +495,9 @@ module Gitlab
rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(0)
end
end
......@@ -512,21 +515,27 @@ module Gitlab
rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(1)
end
end
it "does not return builds if except has current repository path" do
seed_pipeline = pipeline(ref: 'deploy')
config = YAML.dump({
before_script: ["pwd"],
rspec: { script: "rspec", type: type, except: ["branches@path"] }
rspec: {
script: "rspec",
type: type,
except: ["branches@#{seed_pipeline.project_full_path}"]
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
expect(config_processor.pipeline_stage_builds(type, seed_pipeline).size).to eq(0)
end
it "returns builds if except has different repository path" do
......@@ -535,24 +544,28 @@ module Gitlab
rspec: { script: "rspec", type: type, except: ["branches@fork"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1)
end
it "returns build except specified type" do
master_pipeline = pipeline(ref: 'master')
test_pipeline = pipeline(ref: 'test')
deploy_pipeline = pipeline(ref: 'deploy')
config = YAML.dump({
before_script: ["pwd"],
rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@fork"] },
rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@#{test_pipeline.project_full_path}"] },
staging: { script: "deploy", type: "deploy", except: ["master"] },
production: { script: "deploy", type: "deploy", except: ["master@fork"] }
production: { script: "deploy", type: "deploy", except: ["master@#{master_pipeline.project_full_path}"] }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, 'fork')
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
expect(config_processor.builds_for_stage_and_ref("test", "test").size).to eq(0)
expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(0)
expect(config_processor.pipeline_stage_builds("deploy", deploy_pipeline).size).to eq(2)
expect(config_processor.pipeline_stage_builds("test", test_pipeline).size).to eq(0)
expect(config_processor.pipeline_stage_builds("deploy", master_pipeline).size).to eq(0)
end
context 'for invalid value' do
......@@ -591,9 +604,9 @@ module Gitlab
describe "Scripts handling" do
let(:config_data) { YAML.dump(config) }
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config_data, path) }
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config_data) }
subject { config_processor.builds_for_stage_and_ref("test", "master").first }
subject { config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first }
describe "before_script" do
context "in global context" do
......@@ -674,10 +687,10 @@ module Gitlab
before_script: ["pwd"],
rspec: { script: "rspec" } })
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
stage: "test",
stage_idx: 1,
name: "rspec",
......@@ -709,10 +722,10 @@ module Gitlab
command: ["/usr/local/bin/init", "run"] }, "docker:dind"],
script: "rspec" } })
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
stage: "test",
stage_idx: 1,
name: "rspec",
......@@ -742,10 +755,10 @@ module Gitlab
before_script: ["pwd"],
rspec: { script: "rspec" } })
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
stage: "test",
stage_idx: 1,
name: "rspec",
......@@ -771,10 +784,10 @@ module Gitlab
before_script: ["pwd"],
rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } })
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
stage: "test",
stage_idx: 1,
name: "rspec",
......@@ -797,7 +810,7 @@ module Gitlab
end
describe 'Variables' do
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), path) }
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
subject { config_processor.builds.first[:yaml_variables] }
......@@ -918,9 +931,9 @@ module Gitlab
rspec: { script: "rspec", when: when_state }
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
builds = config_processor.builds_for_stage_and_ref("test", "master")
builds = config_processor.pipeline_stage_builds("test", pipeline(ref: "master"))
expect(builds.size).to eq(1)
expect(builds.first[:when]).to eq(when_state)
end
......@@ -951,8 +964,8 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
key: 'key',
......@@ -970,8 +983,8 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
key: 'key',
......@@ -990,8 +1003,8 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq(
paths: ["test/"],
untracked: false,
key: 'local',
......@@ -1019,8 +1032,8 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
stage: "test",
stage_idx: 1,
name: "rspec",
......@@ -1055,9 +1068,9 @@ module Gitlab
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
config_processor = Gitlab::Ci::YamlProcessor.new(config)
builds = config_processor.builds_for_stage_and_ref("test", "master")
builds = config_processor.pipeline_stage_builds("test", pipeline(ref: "master"))
expect(builds.size).to eq(1)
expect(builds.first[:options][:artifacts][:when]).to eq(when_state)
end
......@@ -1072,7 +1085,7 @@ module Gitlab
end
let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
let(:builds) { processor.builds_for_stage_and_ref('deploy', 'master') }
let(:builds) { processor.pipeline_stage_builds('deploy', pipeline(ref: 'master')) }
context 'when a production environment is specified' do
let(:environment) { 'production' }
......@@ -1229,7 +1242,7 @@ module Gitlab
describe "Hidden jobs" do
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) }
subject { config_processor.builds_for_stage_and_ref("test", "master") }
subject { config_processor.pipeline_stage_builds("test", pipeline(ref: "master")) }
shared_examples 'hidden_job_handling' do
it "doesn't create jobs that start with dot" do
......@@ -1277,7 +1290,7 @@ module Gitlab
describe "YAML Alias/Anchor" do
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) }
subject { config_processor.builds_for_stage_and_ref("build", "master") }
subject { config_processor.pipeline_stage_builds("build", pipeline(ref: "master")) }
shared_examples 'job_templates_handling' do
it "is correctly supported for jobs" do
......@@ -1377,182 +1390,182 @@ EOT
it "returns errors if tags parameter is invalid" do
config = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec tags should be an array of strings")
end
it "returns errors if before_script parameter is invalid" do
config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "before_script config should be an array of strings")
end
it "returns errors if job before_script parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array of strings")
end
it "returns errors if after_script parameter is invalid" do
config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "after_script config should be an array of strings")
end
it "returns errors if job after_script parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array of strings")
end
it "returns errors if image parameter is invalid" do
config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "image config should be a hash or a string")
end
it "returns errors if job name is blank" do
config = YAML.dump({ '' => { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:job name can't be blank")
end
it "returns errors if job name is non-string" do
config = YAML.dump({ 10 => { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:10 name should be a symbol")
end
it "returns errors if job image parameter is invalid" do
config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:image config should be a hash or a string")
end
it "returns errors if services parameter is not an array" do
config = YAML.dump({ services: "test", rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "services config should be a array")
end
it "returns errors if services parameter is not an array of strings" do
config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string")
end
it "returns errors if job services parameter is not an array" do
config = YAML.dump({ rspec: { script: "test", services: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:services config should be a array")
end
it "returns errors if job services parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string")
end
it "returns error if job configuration is invalid" do
config = YAML.dump({ extra: "bundle update" })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:extra config should be a hash")
end
it "returns errors if services configuration is not correct" do
config = YAML.dump({ extra: { script: 'rspec', services: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:extra:services config should be a array")
end
it "returns errors if there are no jobs defined" do
config = YAML.dump({ before_script: ["bundle update"] })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs config should contain at least one visible job")
end
it "returns errors if there are no visible jobs defined" do
config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs config should contain at least one visible job")
end
it "returns errors if job allow_failure parameter is not an boolean" do
config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec allow failure should be a boolean value")
end
it "returns errors if job stage is not a string" do
config = YAML.dump({ rspec: { script: "test", type: 1 } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:type config should be a string")
end
it "returns errors if job stage is not a pre-defined stage" do
config = YAML.dump({ rspec: { script: "test", type: "acceptance" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
end
it "returns errors if job stage is not a defined stage" do
config = YAML.dump({ types: %w(build test), rspec: { script: "test", type: "acceptance" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: stage parameter should be build, test")
end
it "returns errors if stages is not an array" do
config = YAML.dump({ stages: "test", rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "stages config should be an array of strings")
end
it "returns errors if stages is not an array of strings" do
config = YAML.dump({ stages: [true, "test"], rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "stages config should be an array of strings")
end
it "returns errors if variables is not a map" do
config = YAML.dump({ variables: "test", rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "variables config should be a hash of key value pairs")
end
it "returns errors if variables is not a map of key-value strings" do
config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "variables config should be a hash of key value pairs")
end
it "returns errors if job when is not on_success, on_failure or always" do
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
Gitlab::Ci::YamlProcessor.new(config, path)
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always or manual")
end
......@@ -1694,6 +1707,10 @@ EOT
end
end
end
def pipeline(**attributes)
build_stubbed(:ci_empty_pipeline, **attributes)
end
end
end
end
......@@ -26,6 +26,7 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
it { is_expected.to delegate_method(:full_path).to(:project).with_prefix }
describe '#source' do
context 'when creating new pipeline' do
......
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