Commit a014e204 authored by Kamil Trzciński's avatar Kamil Trzciński Committed by Grzegorz Bizon
Browse files

Introduce default: for gitlab-ci.yml

This moves all existing `image/services/before_script/variables`
into `default:`. This allows us to easily add a default and
top-level entries. `default`: is keep backward compatible: to
be considered to be job if `default:script:` is specified. This
behavior should be removed.

All existing `image/services/before_script/variables` are properly
handled in root context.
parent b67a802d
---
title: 'Introduce default: for gitlab-ci.yml'
merge_request:
author:
type: added
......@@ -50,6 +50,16 @@ module EE
helpers(*ALLOWED_KEYS)
attributes(*ALLOWED_KEYS)
 
def self.matching?(name, config)
::Feature.enabled?(:cross_project_pipeline_triggers, default_enabled: true) &&
!name.to_s.start_with?('.') &&
config.is_a?(Hash) && config.key?(:trigger)
end
def self.visible?
true
end
def name
@metadata[:name]
end
......
......@@ -6,28 +6,19 @@ module EE
module Config
module Entry
module Jobs
extend ::Gitlab::Utils::Override
extend ActiveSupport::Concern
 
override :node_type
def node_type(name)
if hidden?(name)
::Gitlab::Ci::Config::Entry::Hidden
elsif bridge?(name)
::EE::Gitlab::Ci::Config::Entry::Bridge
else
::Gitlab::Ci::Config::Entry::Job
end
prepended do
EE_TYPES = const_get(:TYPES) + [::EE::Gitlab::Ci::Config::Entry::Bridge]
end
 
def bridge?(name)
config.fetch(name).yield_self do |value|
value.is_a?(Hash) && value.key?(:trigger) &&
cross_project_triggers_enabled?
end
end
class_methods do
extend ::Gitlab::Utils::Override
 
def cross_project_triggers_enabled?
::Feature.enabled?(:cross_project_pipeline_triggers, default_enabled: true)
override :all_types
def all_types
EE_TYPES
end
end
end
end
......
......@@ -2,105 +2,145 @@ require 'fast_spec_helper'
require_dependency 'active_model'
 
describe EE::Gitlab::Ci::Config::Entry::Bridge do
subject { described_class.new(config, name: :my_trigger) }
describe '.matching?' do
subject { described_class.matching?(name, config) }
 
before do
subject.compose!
end
context 'when trigger config is a non-empty string' do
let(:config) { { trigger: 'some/project' } }
context 'when config is not a hash' do
let(:name) { :my_trigger }
let(:config) { 'string' }
 
describe '#valid?' do
it { is_expected.to be_valid }
it { is_expected.to be_falsey }
end
 
describe '#value' do
it 'is returns a bridge job configuration' do
expect(subject.value).to eq(name: :my_trigger,
trigger: { project: 'some/project' },
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] })
context 'when config is a regular job' do
let(:name) { :my_trigger }
let(:config) do
{ script: 'ls -al' }
end
end
end
 
context 'when bridge trigger is a hash' do
let(:config) do
{ trigger: { project: 'some/project', branch: 'feature' } }
it { is_expected.to be_falsey }
end
 
describe '#valid?' do
it { is_expected.to be_valid }
context 'when config is a bridge job' do
let(:name) { :my_trigger }
let(:config) do
{ trigger: 'other-project' }
end
it { is_expected.to be_truthy }
end
 
describe '#value' do
it 'is returns a bridge job configuration hash' do
expect(subject.value).to eq(name: :my_trigger,
trigger: { project: 'some/project',
branch: 'feature' },
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] })
context 'when config is a hidden job' do
let(:name) { '.my_trigger' }
let(:config) do
{ trigger: 'other-project' }
end
it { is_expected.to be_falsey }
end
end
 
context 'when bridge configuration contains all supported keys' do
let(:config) do
{ trigger: { project: 'some/project', branch: 'feature' },
when: 'always',
extends: '.some-key',
stage: 'deploy',
only: { variables: %w[$SOMEVARIABLE] },
except: { refs: %w[feature] },
variables: { VARIABLE: '123' } }
describe '.new' do
subject { described_class.new(config, name: :my_trigger) }
before do
subject.compose!
end
 
it { is_expected.to be_valid }
end
context 'when trigger config is a non-empty string' do
let(:config) { { trigger: 'some/project' } }
 
context 'when trigger config is nil' do
let(:config) { { trigger: nil } }
describe '#valid?' do
it { is_expected.to be_valid }
end
 
describe '#valid?' do
it { is_expected.not_to be_valid }
describe '#value' do
it 'is returns a bridge job configuration' do
expect(subject.value).to eq(name: :my_trigger,
trigger: { project: 'some/project' },
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] })
end
end
end
 
describe '#errors' do
it 'is returns an error about empty trigger config' do
expect(subject.errors.first).to match /can't be blank/
context 'when bridge trigger is a hash' do
let(:config) do
{ trigger: { project: 'some/project', branch: 'feature' } }
end
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'is returns a bridge job configuration hash' do
expect(subject.value).to eq(name: :my_trigger,
trigger: { project: 'some/project',
branch: 'feature' },
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] })
end
end
end
end
 
context 'when bridge config contains unknown keys' do
let(:config) { { unknown: 123 } }
context 'when bridge configuration contains all supported keys' do
let(:config) do
{ trigger: { project: 'some/project', branch: 'feature' },
when: 'always',
extends: '.some-key',
stage: 'deploy',
only: { variables: %w[$SOMEVARIABLE] },
except: { refs: %w[feature] },
variables: { VARIABLE: '123' } }
end
 
describe '#valid?' do
it { is_expected.not_to be_valid }
it { is_expected.to be_valid }
end
 
describe '#errors' do
it 'is returns an error about unknown config key' do
expect(subject.errors.first)
.to match /config contains unknown keys: unknown/
context 'when trigger config is nil' do
let(:config) { { trigger: nil } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about empty trigger config' do
expect(subject.errors.first).to match /can't be blank/
end
end
end
end
 
context 'when bridge config contains build-specific attributes' do
let(:config) { { script: 'something' } }
context 'when bridge config contains unknown keys' do
let(:config) { { unknown: 123 } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
 
describe '#valid?' do
it { is_expected.not_to be_valid }
describe '#errors' do
it 'is returns an error about unknown config key' do
expect(subject.errors.first)
.to match /config contains unknown keys: unknown/
end
end
end
 
describe '#errors' do
it 'returns an error message' do
expect(subject.errors.first)
.to match /contains unknown keys: script/
context 'when bridge config contains build-specific attributes' do
let(:config) { { script: 'something' } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns an error message' do
expect(subject.errors.first)
.to match /contains unknown keys: script/
end
end
end
end
......
require 'spec_helper'
 
describe Gitlab::Ci::Config::Entry::Jobs do
subject do
described_class.new(
{
'.hidden_job'.to_sym => { script: 'something' },
'.hidden_bridge'.to_sym => { trigger: 'my/project' },
regular_job: { script: 'something' },
my_trigger: { trigger: 'my/project' }
}
)
let(:config) do
{
'.hidden_job'.to_sym => { script: 'something' },
'.hidden_bridge'.to_sym => { trigger: 'my/project' },
regular_job: { script: 'something' },
my_trigger: { trigger: 'my/project' }
}
end
 
context 'when cross-project pipeline triggers are enabled' do
before do
stub_feature_flags(cross_project_pipeline_triggers: true)
describe '.all_types' do
subject { described_class.all_types }
 
subject.compose!
end
describe '#node_type' do
it 'correctly identifies hidden jobs' do
expect(subject.node_type(:'.hidden_job'))
.to eq ::Gitlab::Ci::Config::Entry::Hidden
end
it { is_expected.to include(::EE::Gitlab::Ci::Config::Entry::Bridge) }
end
 
it 'correctly identifies hidden bridge jobs' do
expect(subject.node_type(:'.hidden_bridge'))
.to eq ::Gitlab::Ci::Config::Entry::Hidden
end
describe '.find_type' do
using RSpec::Parameterized::TableSyntax
 
it 'correctly identifies regular jobs' do
expect(subject.node_type(:regular_job))
.to eq ::Gitlab::Ci::Config::Entry::Job
end
subject { described_class.find_type(name, config[name]) }
 
it 'correctly identifies cross-project triggers' do
expect(subject.node_type(:my_trigger))
.to eq ::EE::Gitlab::Ci::Config::Entry::Bridge
context 'when cross-project pipeline triggers are enabled' do
before do
stub_feature_flags(cross_project_pipeline_triggers: true)
end
end
 
describe '#bridge?' do
it 'returns true when a job is a trigger' do
expect(subject.bridge?(:my_trigger)).to be true
where(:name, :type) do
:'.hidden_job' | ::Gitlab::Ci::Config::Entry::Hidden
:'.hidden_bridge' | ::Gitlab::Ci::Config::Entry::Hidden
:regular_job | ::Gitlab::Ci::Config::Entry::Job
:my_trigger | ::EE::Gitlab::Ci::Config::Entry::Bridge
end
 
it 'returns false when a job is not a trigger' do
expect(subject.bridge?(:regular_job)).to be false
with_them do
it { is_expected.to eq(type) }
end
end
 
describe '#hidden?' do
it 'does not claim that a bridge job is hidden' do
expect(subject.hidden?(:my_trigger)).to be false
context 'when cross-project pipeline triggers are disabled' do
before do
stub_feature_flags(cross_project_pipeline_triggers: false)
end
end
 
describe '#valid?' do
it { is_expected.to be_valid }
end
where(:name, :type) do
:'.hidden_job' | ::Gitlab::Ci::Config::Entry::Hidden
:'.hidden_bridge' | ::Gitlab::Ci::Config::Entry::Hidden
:regular_job | ::Gitlab::Ci::Config::Entry::Job
:my_trigger | nil
end
 
describe '#value' do
it 'returns a correct hash representing all jobs' do
expect(subject.value).to eq(
my_trigger: {
name: :my_trigger,
trigger: { project: 'my/project' },
stage: 'test',
only: { refs: %w[branches tags] },
ignore: false
},
regular_job: {
script: %w[something],
name: :regular_job,
stage: 'test',
only: { refs: %w[branches tags] },
ignore: false
})
with_them do
it { is_expected.to eq(type) }
end
end
end
 
context 'when cross-project pipeline triggers are disabled' do
before do
stub_feature_flags(cross_project_pipeline_triggers: false)
subject.compose!
describe '.new' do
subject do
described_class.new(config)
end
 
describe '#node_type' do
it 'correctly identifies hidden jobs' do
expect(subject.node_type(:'.hidden_job'))
.to eq ::Gitlab::Ci::Config::Entry::Hidden
context 'when cross-project pipeline triggers are enabled' do
before do
stub_feature_flags(cross_project_pipeline_triggers: true)
subject.compose!
end
 
it 'correctly identifies regular jobs' do
expect(subject.node_type(:regular_job))
.to eq ::Gitlab::Ci::Config::Entry::Job
describe '#valid?' do
it { is_expected.to be_valid }
end
 
it 'does not identify trigger job as a bridge job' do
expect(subject.node_type(:my_trigger))
.to eq ::Gitlab::Ci::Config::Entry::Job
describe '#value' do
it 'returns a correct hash representing all jobs' do
expect(subject.value).to eq(
my_trigger: {
name: :my_trigger,
trigger: { project: 'my/project' },
stage: 'test',
only: { refs: %w[branches tags] },
ignore: false
},
regular_job: {
script: %w[something],
name: :regular_job,
stage: 'test',
only: { refs: %w[branches tags] },
variables: {},
ignore: false
})
end
end
end
 
describe '#bridge?' do
it 'returns false even when a job is a trigger' do
expect(subject.bridge?(:my_trigger)).to be false
end
context 'when cross-project pipeline triggers are disabled' do
before do
stub_feature_flags(cross_project_pipeline_triggers: false)
 
it 'returns false when a job is not a trigger' do
expect(subject.bridge?(:regular_job)).to be false
subject.compose!
end
end
 
describe '#valid?' do
it { is_expected.not_to be_valid }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
end
end
end
......@@ -14,23 +14,25 @@ module Gitlab
External::Processor::IncludeError
].freeze
 
attr_reader :root
def initialize(config, project: nil, sha: nil, user: nil)
@config = Config::Extendable
.new(build_config(config, project: project, sha: sha, user: user))
.to_hash
 
@global = Entry::Global.new(@config)
@global.compose!
@root = Entry::Root.new(@config)
@root.compose!
rescue *rescue_errors => e
raise Config::ConfigError, e.message
end
 
def valid?
@global.valid?
@root.valid?
end
 
def errors
@global.errors
@root.errors
end
 
def to_hash
......@@ -40,36 +42,16 @@ module Gitlab
##
# Temporary method that should be removed after refactoring
#
def before_script
@global.before_script_value
end
def image
@global.image_value
end
def services
@global.services_value
end
def after_script
@global.after_script_value
end
def variables
@global.variables_value
root.variables_value
end
 
def stages
@global.stages_value
end
def cache
@global.cache_value
root.stages_value
end
 
def jobs
@global.jobs_value
root.jobs_value
end
 
private
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
##
# This class represents a default entry
# Entry containing default values for all jobs
# defined in configuration file.
#
class Default < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
DuplicateError = Class.new(Gitlab::Config::Loader::FormatError)
ALLOWED_KEYS = %i[before_script image services
after_script cache].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
end
entry :before_script, Entry::Script,
description: 'Script that will be executed before each job.',
inherit: true
entry :image, Entry::Image,
description: 'Docker image that will be used to execute jobs.',
inherit: true
entry :services, Entry::Services,
description: 'Docker images that will be linked to the container.',
inherit: true
entry :after_script, Entry::Script,
description: 'Script that will be executed after each job.',
inherit: true
entry :cache, Entry::Cache,
description: 'Configure caching between build jobs.',
inherit: true
helpers :before_script, :image, :services, :after_script, :cache
def compose!(deps = nil)
super(self)
inherit!(deps)
end
private
def inherit!(deps)
return unless deps
self.class.nodes.each do |key, factory|
next unless factory.inheritable?