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

Allow to disable defaults

This introduces job/bridge option `inherit:`
that disables inheritance of variables and defined
defaults.
parent 4b8337d4
---
title: Allow to disable inheritance of default job settings
merge_request: 25690
author:
type: added
......@@ -158,6 +158,42 @@ rspec 2.6:
script: bundle exec rspec
```
### `inherit`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/207484) in GitLab 12.9.
You can disable inheritance of globally defined defaults
and variables with the `inherit:` parameter.
In the example below:
- `rubocop` **will** inherit both the `before_script` and the variable `DOMAIN`.
- `rspec` **will not** inherit the `before_script` or the variable `DOMAIN`.
- `capybara` **will** inherit the `before_script`, but **will not** inherit the variable `DOMAIN`.
```yaml
default:
before_script:
- echo Hello World
variables:
DOMAIN: example.com
rubocop:
script: bundle exec rubocop
rspec:
inherit:
default: false
variables: false
script: bundle exec rspec
capybara:
inherit:
variables: false
script: bundle exec capybara
```
## Parameter details
The following are detailed explanations for parameters used to configure CI/CD pipelines.
......
......@@ -47,6 +47,7 @@ describe Gitlab::Ci::Config::Entry::Bridge do
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] },
variables: {},
scheduling_type: :stage)
end
end
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
##
# This class represents a inherit entry
#
class Inherit < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
ALLOWED_KEYS = %i[default variables].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
end
entry :default, ::Gitlab::Config::Entry::Boolean,
description: 'Indicates whether to inherit `default:`.',
default: true
entry :variables, ::Gitlab::Config::Entry::Boolean,
description: 'Indicates whether to inherit `variables:`.',
default: true
end
end
end
end
end
......@@ -13,7 +13,7 @@ module Gitlab
ALLOWED_WHEN = %w[on_success on_failure always manual delayed].freeze
ALLOWED_KEYS = %i[tags script type image services
allow_failure type when start_in artifacts cache
dependencies before_script needs after_script variables
dependencies before_script needs after_script
environment coverage retry parallel interruptible timeout
resource_group release].freeze
......
......@@ -14,7 +14,7 @@ module Gitlab
include ::Gitlab::Config::Entry::Attributable
include ::Gitlab::Config::Entry::Inheritable
PROCESSABLE_ALLOWED_KEYS = %i[extends stage only except rules variables].freeze
PROCESSABLE_ALLOWED_KEYS = %i[extends stage only except rules variables inherit].freeze
included do
validations do
......@@ -58,12 +58,17 @@ module Gitlab
description: 'Environment variables available for this job.',
inherit: false
entry :inherit, ::Gitlab::Ci::Config::Entry::Inherit,
description: 'Indicates whether to inherit defaults or not.',
inherit: false,
default: {}
attributes :extends, :rules
end
def compose!(deps = nil)
super do
has_workflow_rules = deps&.workflow&.has_rules?
has_workflow_rules = deps&.workflow_entry&.has_rules?
# If workflow:rules: or rules: are used
# they are considered not compatible
......@@ -77,6 +82,9 @@ module Gitlab
@entries.delete(:except) unless except_defined? # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
# inherit root variables
@root_variables_value = deps&.variables_value # rubocop:disable Gitlab/ModuleWithInstanceVariables
yield if block_given?
end
end
......@@ -86,7 +94,10 @@ module Gitlab
end
def overwrite_entry(deps, key, current_entry)
deps.default[key] unless current_entry.specified?
return unless inherit_entry&.default_value
return unless deps.default_entry
deps.default_entry[key] unless current_entry.specified?
end
def value
......@@ -94,10 +105,18 @@ module Gitlab
stage: stage_value,
extends: extends,
rules: rules_value,
variables: variables_defined? ? variables_value : {},
variables: root_and_job_variables_value,
only: only_value,
except: except_value }.compact
end
def root_and_job_variables_value
if inherit_entry&.variables_value
@root_variables_value.to_h.merge(variables_value.to_h) # rubocop:disable Gitlab/ModuleWithInstanceVariables
else
variables_value.to_h
end
end
end
end
end
......
......@@ -65,7 +65,8 @@ module Gitlab
reserved: true
entry :workflow, Entry::Workflow,
description: 'List of evaluable rules to determine Pipeline status'
description: 'List of evaluable rules to determine Pipeline status',
default: {}
dynamic_helpers :jobs
......@@ -73,7 +74,7 @@ module Gitlab
:image_value,
:services_value,
:after_script_value,
:cache_value, to: :default
:cache_value, to: :default_entry
attr_reader :jobs_config
......@@ -102,14 +103,6 @@ module Gitlab
end
end
def default
self[:default]
end
def workflow
self[:workflow] if workflow_defined?
end
private
# rubocop: disable CodeReuse/ActiveRecord
......
......@@ -12,7 +12,6 @@ module Gitlab
validations do
validates :config, type: Hash
validates :config, allowed_keys: ALLOWED_KEYS
validates :config, presence: true
end
entry :rules, Entry::Rules,
......
......@@ -57,7 +57,7 @@ module Gitlab
when: job[:when] || 'on_success',
environment: job[:environment_name],
coverage_regex: job[:coverage],
yaml_variables: transform_to_yaml_variables(job_variables(name)),
yaml_variables: transform_to_yaml_variables(job[:variables]),
needs_attributes: job.dig(:needs, :job),
interruptible: job[:interruptible],
only: job[:only],
......@@ -146,13 +146,6 @@ module Gitlab
end
end
def job_variables(name)
job_variables = @jobs.dig(name.to_sym, :variables)
@variables.to_h
.merge(job_variables.to_h)
end
def transform_to_yaml_variables(variables)
variables.to_h.map do |key, value|
{ key: key.to_s, value: value, public: true }
......
......@@ -10,7 +10,7 @@ module Gitlab
def attributes(*attributes)
attributes.flatten.each do |attribute|
if method_defined?(attribute)
raise ArgumentError, "Method already defined: #{attribute}"
raise ArgumentError, "Method '#{attribute}' already defined in '#{name}'"
end
define_method(attribute) do
......
......@@ -76,7 +76,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def entry(key, entry, description: nil, default: nil, inherit: nil, reserved: nil, metadata: {})
entry_name = key.to_sym
raise ArgumentError, "Entry #{key} already defined" if @nodes.to_h[entry_name]
raise ArgumentError, "Entry '#{key}' already defined in '#{name}'" if @nodes.to_h[entry_name]
factory = ::Gitlab::Config::Entry::Factory.new(entry)
.with(description: description)
......@@ -98,8 +98,8 @@ module Gitlab
def helpers(*nodes, dynamic: false)
nodes.each do |symbol|
if method_defined?("#{symbol}_defined?") || method_defined?("#{symbol}_value")
raise ArgumentError, "Method #{symbol}_defined? or #{symbol}_value already defined"
if method_defined?("#{symbol}_defined?") || method_defined?("#{symbol}_entry") || method_defined?("#{symbol}_value")
raise ArgumentError, "Method '#{symbol}_defined?', '#{symbol}_entry' or '#{symbol}_value' already defined in '#{name}'"
end
unless @nodes.to_h[symbol]
......@@ -110,10 +110,13 @@ module Gitlab
entries[symbol]&.specified?
end
define_method("#{symbol}_value") do
return unless entries[symbol] && entries[symbol].valid?
define_method("#{symbol}_entry") do
entries[symbol]
end
entries[symbol].value
define_method("#{symbol}_value") do
entry = entries[symbol]
entry.value if entry&.valid?
end
end
end
......
......@@ -6,6 +6,7 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) }
it_behaves_like 'with inheritable CI config' do
let(:config) { { script: 'echo' } }
let(:inheritable_key) { 'default' }
let(:inheritable_class) { Gitlab::Ci::Config::Entry::Default }
......@@ -15,6 +16,10 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:ignored_inheritable_columns) do
%i[]
end
before do
allow(entry).to receive_message_chain(:inherit_entry, :default_value).and_return(true)
end
end
describe '.nodes' do
......@@ -24,7 +29,8 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:result) do
%i[before_script script stage type after_script cache
image services only except rules needs variables artifacts
environment coverage retry interruptible timeout release tags]
environment coverage retry interruptible timeout release tags
inherit]
end
it { is_expected.to match_array result }
......@@ -500,7 +506,13 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:unspecified) { double('unspecified', 'specified?' => false) }
let(:default) { double('default', '[]' => unspecified) }
let(:workflow) { double('workflow', 'has_rules?' => false) }
let(:deps) { double('deps', 'default' => default, '[]' => unspecified, 'workflow' => workflow) }
let(:deps) do
double('deps',
'default_entry' => default,
'workflow_entry' => workflow,
'variables_value' => nil)
end
context 'when job config overrides default config' do
before do
......
......@@ -7,6 +7,10 @@ describe Gitlab::Ci::Config::Entry::Processable do
Class.new(::Gitlab::Config::Entry::Node) do
include Gitlab::Ci::Config::Entry::Processable
entry :tags, ::Gitlab::Config::Entry::ArrayOfStrings,
description: 'Set the default tags.',
inherit: true
def self.name
'job'
end
......@@ -189,14 +193,17 @@ describe Gitlab::Ci::Config::Entry::Processable do
end
describe '#compose!' do
let(:specified) do
double('specified', 'specified?' => true, value: 'specified')
end
let(:unspecified) { double('unspecified', 'specified?' => false) }
let(:default) { double('default', '[]' => unspecified) }
let(:workflow) { double('workflow', 'has_rules?' => false) }
let(:deps) { double('deps', 'default' => default, '[]' => unspecified, 'workflow' => workflow) }
let(:variables) { }
let(:deps) do
double('deps',
default_entry: default,
workflow_entry: workflow,
variables_value: variables)
end
context 'with workflow rules' do
using RSpec::Parameterized::TableSyntax
......@@ -240,6 +247,84 @@ describe Gitlab::Ci::Config::Entry::Processable do
end
end
end
context 'with inheritance' do
context 'of variables' do
let(:config) do
{ variables: { A: 'job', B: 'job' } }
end
before do
entry.compose!(deps)
end
context 'with only job variables' do
it 'does return defined variables' do
expect(entry.value).to include(
variables: { 'A' => 'job', 'B' => 'job' }
)
end
end
context 'when root yaml variables are used' do
let(:variables) do
Gitlab::Ci::Config::Entry::Variables.new(
A: 'root', C: 'root'
).value
end
it 'does return all variables and overwrite them' do
expect(entry.value).to include(
variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root' }
)
end
context 'when inherit of defaults is disabled' do
let(:config) do
{
variables: { A: 'job', B: 'job' },
inherit: { variables: false }
}
end
it 'does return only job variables' do
expect(entry.value).to include(
variables: { 'A' => 'job', 'B' => 'job' }
)
end
end
end
end
context 'of default:tags' do
using RSpec::Parameterized::TableSyntax
where(:default_tags, :tags, :inherit_default, :result) do
nil | %w[a b] | nil | %w[a b]
nil | %w[a b] | true | %w[a b]
nil | %w[a b] | false | %w[a b]
%w[b c] | %w[a b] | nil | %w[a b]
%w[b c] | %w[a b] | true | %w[a b]
%w[b c] | %w[a b] | false | %w[a b]
%w[b c] | nil | nil | %w[b c]
%w[b c] | nil | true | %w[b c]
%w[b c] | nil | false | nil
end
with_them do
let(:config) { { tags: tags, inherit: { default: inherit_default } } }
let(:default_specified_tags) { double('tags', 'specified?' => true, 'valid?' => true, 'value' => default_tags) }
before do
allow(default).to receive('[]').with(:tags).and_return(default_specified_tags)
entry.compose!(deps)
end
it { expect(entry.tags_value).to eq(result) }
end
end
end
end
context 'when composed' do
......@@ -254,10 +339,12 @@ describe Gitlab::Ci::Config::Entry::Processable do
end
it 'returns correct value' do
expect(entry.value)
.to eq(name: :rspec,
expect(entry.value).to eq(
name: :rspec,
stage: 'test',
only: { refs: %w[branches tags] })
only: { refs: %w[branches tags] },
variables: {}
)
end
end
end
......
......@@ -32,7 +32,7 @@ describe Gitlab::Ci::Config::Entry::Root do
image: 'ruby:2.2',
default: {},
services: ['postgres:9.1', 'mysql:5.5'],
variables: { VAR: 'value' },
variables: { VAR: 'root' },
after_script: ['make clean'],
stages: %w(build pages release),
cache: { key: 'k', untracked: true, paths: ['public/'] },
......@@ -42,6 +42,7 @@ describe Gitlab::Ci::Config::Entry::Root do
stage: 'release',
before_script: [],
after_script: [],
variables: { 'VAR' => 'job' },
script: ["make changelog | tee release_changelog.txt"],
release: {
tag_name: 'v0.06',
......@@ -127,7 +128,7 @@ describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
variables: {},
variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
......@@ -141,7 +142,7 @@ describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
variables: {},
variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
......@@ -157,7 +158,7 @@ describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push" },
only: { refs: %w(branches tags) },
variables: {},
variables: { 'VAR' => 'job' },
after_script: [],
ignore: false,
scheduling_type: :stage }
......@@ -175,11 +176,11 @@ describe Gitlab::Ci::Config::Entry::Root do
image: 'ruby:2.1',
services: ['postgres:9.1', 'mysql:5.5']
},
variables: { VAR: 'value' },
variables: { VAR: 'root' },
stages: %w(build pages),
cache: { key: 'k', untracked: true, paths: ['public/'] },
rspec: { script: %w[rspec ls] },
spinach: { before_script: [], variables: { VAR: 'AA' }, script: 'spinach' } }
spinach: { before_script: [], variables: { VAR: 'job' }, script: 'spinach' } }
end
context 'when composed' do
......@@ -203,7 +204,7 @@ describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" },
variables: {},
variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
......@@ -215,7 +216,7 @@ describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" },
variables: { 'VAR' => 'AA' },
variables: { 'VAR' => 'job' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
......
......@@ -509,28 +509,44 @@ module Gitlab
describe "before_script" do
context "in global context" do
using RSpec::Parameterized::TableSyntax
where(:inherit, :result) do
nil | ["global script"]
{ default: false } | nil
{ default: true } | ["global script"]
end
with_them do
let(:config) do
{
before_script: ["global script"],
test: { script: ["script"] }
test: { script: ["script"], inherit: inherit }
}
end
it "return commands with scripts concatenated" do
expect(subject[:options][:before_script]).to eq(["global script"])
it { expect(subject[:options][:before_script]).to eq(result) }
end
end
context "in default context" do
using RSpec::Parameterized::TableSyntax
where(:inherit, :result) do
nil | ["global script"]
{ default: false } | nil
{ default: true } | ["global script"]
end
with_them do
let(:config) do
{
default: { before_script: ["global script"] },
test: { script: ["script"] }
test: { script: ["script"], inherit: inherit }
}
end
it "return commands with scripts concatenated" do
expect(subject[:options][:before_script]).to eq(["global script"])
it { expect(subject[:options][:before_script]).to eq(result) }
end
end
......@@ -793,7 +809,7 @@ module Gitlab
context 'when job and global variables are defined' do
let(:global_variables) do
{ 'VAR1' => 'global1', 'VAR3' => 'global3' }
{ 'VAR1' => 'global1', 'VAR3' => 'global3', 'VAR4' => 'global4' }
end
let(:job_variables) do
{ 'VAR1' => 'value1', 'VAR2' => 'value2' }
......@@ -802,12 +818,16 @@ module Gitlab
{
before_script: ['pwd'],
variables: global_variables,
rspec: { script: 'rspec', variables: job_variables }
rspec: { script: 'rspec', variables: job_variables, inherit: inherit }
}
end
context 'when no inheritance is specified' do
let(:inherit) { }
it 'returns all unique variables' do
expect(subject).to contain_exactly(
{ key: 'VAR4', value: 'global4', public: true },
{ key: 'VAR3', value: 'global3', public: true },
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
......@@ -815,6 +835,18 @@ module Gitlab
end
end
context 'when inheritance is disabled' do
let(:inherit) { { variables: false } }
it 'does not inherit variables' do
expect(subject).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
end
end
end
context 'when job variables are defined' do
let(:config) do
{
......
......@@ -59,7 +59,7 @@ describe Gitlab::Config::Entry::Attributable do
end
end
expectation.to raise_error(ArgumentError, 'Method already defined: length')
expectation.to raise_error(ArgumentError, /Method 'length' already defined in/)
end
end
end
......@@ -53,7 +53,7 @@ RSpec.shared_examples 'with inheritable CI config' do
let(:deps) do
if inheritable_key
double('deps', inheritable_key => inheritable, '[]' => unspecified)
double('deps', "#{inheritable_key}_entry" => inheritable, '[]' => unspecified)
else
inheritable
end
......@@ -68,7 +68,7 @@ RSpec.shared_examples 'with inheritable CI config' do
it 'does inherit value' do
expect(inheritable).to receive('[]').with(entry_key).and_return(specified)
entry.compose!(deps)
entry.send(:inherit!, deps)
expect(entry[entry_key]).to eq(specified)
end
......@@ -86,7 +86,7 @@ RSpec.shared_examples 'with inheritable CI config' do
expect do
# we ignore exceptions as `#overwrite_entry`
# can raise exception on duplicates
entry.compose!(deps) rescue described_class::InheritError
entry.send(:inherit!, deps) rescue described_class::InheritError
end.not_to change { entry[entry_key] }
end
end
......@@ -94,7 +94,7 @@ RSpec.shared_examples 'with inheritable CI config' do
context 'when inheritable does not specify' do
it 'does not inherit value' do
entry.compose!(deps)
entry.send(:inherit!, deps)
expect(entry[entry_key]).to be_a(
Gitlab::Config::Entry::Undefined)
......
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