Commit 58414c14 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Support recursive `extends:` in `.gitlab-ci.yml`

parent 5b9a6ca0
module Gitlab
module Ci
class Config
class Extendable
include Enumerable
ExtensionError = Class.new(StandardError)
def initialize(hash)
@hash = hash
end
def each
@hash.each_pair do |key, value|
next unless value.key?(:extends)
yield key, value.fetch(:extends).to_sym, value
end
end
def extend!
@hash.tap do
each do |key, extends, value|
@hash[key] = @hash.fetch(extends).deep_merge(value)
end
end
end
end
end
end
end
module Gitlab
module Ci
class Config
module Extendable
class Collection
include Enumerable
ExtensionError = Class.new(StandardError)
def initialize(hash, context = hash)
@hash = hash
@context = context
end
def each
@hash.each_pair do |key, value|
next unless value.key?(:extends)
yield Extendable::Entry.new(key, value, @context)
end
end
def extend!
each do |entry|
raise ExtensionError unless entry.valid?
@hash[entry.key] = entry.extend!
end
end
end
end
end
end
end
module Gitlab
module Ci
class Config
module Extendable
class Entry
attr_reader :key
def initialize(key, value, context, parent = nil)
@key = key
@value = value
@context = context
@parent = parent
end
def valid?
true
end
# def circular_dependency?
# @extends.to_s == @key.to_s
# end
def base
Extendable::Entry
.new(extends, @context.fetch(extends), @context, self)
.extend!
end
def extensible?
@value.key?(:extends)
end
def extends
@value.fetch(:extends).to_sym
end
def extend!
if extensible?
original = @value.dup
parent = base.dup
@value.clear.deep_merge!(parent).deep_merge!(original)
else
@value.to_h
end
end
end
end
end
end
end
require 'fast_spec_helper' require 'fast_spec_helper'
describe Gitlab::Ci::Config::Extendable do describe Gitlab::Ci::Config::Extendable::Collection do
subject { described_class.new(hash) } subject { described_class.new(hash) }
describe '#each' do describe '#each' do
...@@ -14,12 +14,19 @@ describe Gitlab::Ci::Config::Extendable do ...@@ -14,12 +14,19 @@ describe Gitlab::Ci::Config::Extendable do
end end
it 'yields the test hash' do it 'yields the test hash' do
expect { |b| subject.each(&b) } expect { |b| subject.each(&b) }.to yield_control
.to yield_with_args(:test, :something, test)
end end
end end
pending 'when not extending using a hash' context 'when not extending using a hash' do
let(:hash) do
{ something: { extends: [1], script: 'ls' } }
end
it 'yields invalid extends as well' do
expect { |b| subject.each(&b) }.to yield_control
end
end
end end
describe '#extend!' do describe '#extend!' do
...@@ -30,6 +37,7 @@ describe Gitlab::Ci::Config::Extendable do ...@@ -30,6 +37,7 @@ describe Gitlab::Ci::Config::Extendable do
script: 'deploy', script: 'deploy',
only: { variables: %w[$SOMETHING] } only: { variables: %w[$SOMETHING] }
}, },
test: { test: {
extends: 'something', extends: 'something',
script: 'ls', script: 'ls',
...@@ -38,12 +46,13 @@ describe Gitlab::Ci::Config::Extendable do ...@@ -38,12 +46,13 @@ describe Gitlab::Ci::Config::Extendable do
} }
end end
it 'extends a hash with reverse merge' do it 'extends a hash with a deep reverse merge' do
expect(subject.extend!).to eq( expect(subject.extend!).to eq(
something: { something: {
script: 'deploy', script: 'deploy',
only: { variables: %w[$SOMETHING] } only: { variables: %w[$SOMETHING] }
}, },
test: { test: {
extends: 'something', extends: 'something',
script: 'ls', script: 'ls',
...@@ -56,8 +65,58 @@ describe Gitlab::Ci::Config::Extendable do ...@@ -56,8 +65,58 @@ describe Gitlab::Ci::Config::Extendable do
end end
end end
pending 'when a hash recursive extensions' context 'when a hash uses recursive extensions' do
let(:hash) do
{
test: {
extends: 'something',
script: 'ls',
only: { refs: %w[master] }
},
something: {
extends: '.first',
script: 'deploy',
only: { variables: %w[$SOMETHING] }
},
'.first': {
script: 'run',
only: { kubernetes: 'active' }
}
}
end
it 'extends a hash with a deep reverse merge' do
expect(subject.extend!).to eq(
'.first': {
script: 'run',
only: { kubernetes: 'active' }
},
something: {
extends: '.first',
script: 'deploy',
only: {
kubernetes: 'active',
variables: %w[$SOMETHING]
}
},
test: {
extends: 'something',
script: 'ls',
only: {
refs: %w[master],
variables: %w[$SOMETHING],
kubernetes: 'active'
}
}
)
end
end
pending 'when invalid `extends` is specified' pending 'when invalid `extends` is specified'
pending 'when circular dependecy has been detected'
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