Support recursive extends: in .gitlab-ci.yml

This commit is contained in:
Grzegorz Bizon 2018-08-16 14:48:38 +02:00
parent 5b9a6ca00a
commit 58414c143f
5 changed files with 207 additions and 94 deletions

View file

@ -1,31 +0,0 @@
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

View file

@ -0,0 +1,34 @@
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

View file

@ -0,0 +1,51 @@
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

View file

@ -0,0 +1,122 @@
require 'fast_spec_helper'
describe Gitlab::Ci::Config::Extendable::Collection do
subject { described_class.new(hash) }
describe '#each' do
context 'when there is extendable entry in the hash' do
let(:test) do
{ extends: 'something', only: %w[master] }
end
let(:hash) do
{ something: { script: 'ls' }, test: test }
end
it 'yields the test hash' do
expect { |b| subject.each(&b) }.to yield_control
end
end
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
describe '#extend!' do
context 'when a hash has a single simple extension' do
let(:hash) do
{
something: {
script: 'deploy',
only: { variables: %w[$SOMETHING] }
},
test: {
extends: 'something',
script: 'ls',
only: { refs: %w[master] }
}
}
end
it 'extends a hash with a deep reverse merge' do
expect(subject.extend!).to eq(
something: {
script: 'deploy',
only: { variables: %w[$SOMETHING] }
},
test: {
extends: 'something',
script: 'ls',
only: {
refs: %w[master],
variables: %w[$SOMETHING]
}
}
)
end
end
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 circular dependecy has been detected'
end
end

View file

@ -1,63 +0,0 @@
require 'fast_spec_helper'
describe Gitlab::Ci::Config::Extendable do
subject { described_class.new(hash) }
describe '#each' do
context 'when there is extendable entry in the hash' do
let(:test) do
{ extends: 'something', only: %w[master] }
end
let(:hash) do
{ something: { script: 'ls' }, test: test }
end
it 'yields the test hash' do
expect { |b| subject.each(&b) }
.to yield_with_args(:test, :something, test)
end
end
pending 'when not extending using a hash'
end
describe '#extend!' do
context 'when a hash has a single simple extension' do
let(:hash) do
{
something: {
script: 'deploy',
only: { variables: %w[$SOMETHING] }
},
test: {
extends: 'something',
script: 'ls',
only: { refs: %w[master] }
}
}
end
it 'extends a hash with reverse merge' do
expect(subject.extend!).to eq(
something: {
script: 'deploy',
only: { variables: %w[$SOMETHING] }
},
test: {
extends: 'something',
script: 'ls',
only: {
refs: %w[master],
variables: %w[$SOMETHING]
}
}
)
end
end
pending 'when a hash recursive extensions'
pending 'when invalid `extends` is specified'
end
end