Commit 5f33690e authored by Matija Čupić's avatar Matija Čupić

Load external files in config

CE mirror of 8e03a6619be44fdaf19a6c13284ea8e51377b311
parent c3e33f06
module Gitlab module Gitlab
module Ci module Ci
## #
# Base GitLab CI Configuration facade # Base GitLab CI Configuration facade
# #
class Config class Config
ConfigError = Class.new(StandardError) ConfigError = Class.new(StandardError)
def initialize(config, opts = {}) def initialize(config, opts = {})
@config = Config::Extendable initial_config = Config::Extendable
.new(build_config(config, opts)) .new(build_config(config, opts))
.to_hash .to_hash
processor = ::Gitlab::Ci::ExternalFiles::Processor.new(initial_config)
@config = processor.perform
@global = Entry::Global.new(@config) @global = Entry::Global.new(@config)
@global.compose! @global.compose!
......
...@@ -33,15 +33,11 @@ module Gitlab ...@@ -33,15 +33,11 @@ module Gitlab
entry :cache, Entry::Cache, entry :cache, Entry::Cache,
description: 'Configure caching between build jobs.' description: 'Configure caching between build jobs.'
entry :includes, Entry::Includes,
description: 'External GitlLab Ci files'
helpers :before_script, :image, :services, :after_script, helpers :before_script, :image, :services, :after_script,
:variables, :stages, :types, :cache, :jobs, :includes :variables, :stages, :types, :cache, :jobs
def compose!(_deps = nil) def compose!(_deps = nil)
super(self) do super(self) do
append_external_files!
compose_jobs! compose_jobs!
compose_deprecated_entries! compose_deprecated_entries!
end end
...@@ -49,10 +45,6 @@ module Gitlab ...@@ -49,10 +45,6 @@ module Gitlab
private private
def append_external_files!
return if includes_value.nil?
end
def compose_jobs! def compose_jobs!
factory = Entry::Factory.new(Entry::Jobs) factory = Entry::Factory.new(Entry::Jobs)
.value(@config.except(*self.class.nodes.keys)) .value(@config.except(*self.class.nodes.keys))
......
module Gitlab
module Ci
class Config
module Entry
##
# Entry that represents a Docker image.
#
class Includes < Node
include Validatable
validations do
validates :config, array_or_string: true, external_file: true, allow_nil: true
end
def value
Array(@config)
end
end
end
end
end
end
...@@ -60,38 +60,6 @@ module Gitlab ...@@ -60,38 +60,6 @@ module Gitlab
end end
end end
class ArrayOrStringValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.is_a?(Array) || value.is_a?(String)
record.errors.add(attribute, 'should be an array or a string')
end
end
end
class ExternalFileValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if value.is_a?(Array)
value.each do |path|
validate_external_file(path, record, attribute)
end
else
validate_external_file(value, record, attribute)
end
end
private
def validate_external_file(value, record, attribute)
unless valid_url?(value)
record.errors.add(attribute, 'should be a valid local or remote file')
end
end
def valid_url?(value)
Gitlab::UrlSanitizer.valid?(value) || File.exists?("#{Rails.root}/#{value}")
end
end
class KeyValidator < ActiveModel::EachValidator class KeyValidator < ActiveModel::EachValidator
include LegacyValidationHelpers include LegacyValidationHelpers
......
...@@ -4,7 +4,6 @@ module Gitlab ...@@ -4,7 +4,6 @@ module Gitlab
module Ci module Ci
module ExternalFiles module ExternalFiles
class ExternalFile class ExternalFile
def initialize(value) def initialize(value)
@value = value @value = value
end end
...@@ -18,7 +17,7 @@ module Gitlab ...@@ -18,7 +17,7 @@ module Gitlab
end end
def valid? def valid?
remote_url? || File.exists?(base_path) remote_url? || File.exist?(base_path)
end end
private private
......
...@@ -2,14 +2,11 @@ module Gitlab ...@@ -2,14 +2,11 @@ module Gitlab
module Ci module Ci
module ExternalFiles module ExternalFiles
class Mapper class Mapper
def self.fetch_paths(values) def self.fetch_paths(values)
paths = values.fetch(:includes, []) paths = values.fetch(:includes, [])
normalize_paths(paths) normalize_paths(paths)
end end
private
def self.normalize_paths(paths) def self.normalize_paths(paths)
if paths.is_a?(String) if paths.is_a?(String)
[build_external_file(paths)] [build_external_file(paths)]
......
...@@ -39,7 +39,6 @@ module Gitlab ...@@ -39,7 +39,6 @@ module Gitlab
values.delete(:includes) values.delete(:includes)
values values
end end
end end
end end
end end
......
...@@ -13,7 +13,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -13,7 +13,7 @@ describe Gitlab::Ci::Config::Entry::Global do
expect(described_class.nodes.keys) expect(described_class.nodes.keys)
.to match_array(%i[before_script image services .to match_array(%i[before_script image services
after_script variables stages after_script variables stages
types cache includes]) types cache])
end end
end end
end end
...@@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::Entry::Global do
end end
it 'creates node object for each entry' do it 'creates node object for each entry' do
expect(global.descendants.count).to eq 9 expect(global.descendants.count).to eq 8
end end
it 'creates node object using valid class' do it 'creates node object using valid class' do
...@@ -189,7 +189,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -189,7 +189,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#nodes' do describe '#nodes' do
it 'instantizes all nodes' do it 'instantizes all nodes' do
expect(global.descendants.count).to eq 9 expect(global.descendants.count).to eq 8
end end
it 'contains unspecified nodes' do it 'contains unspecified nodes' do
......
require 'rails_helper'
describe Gitlab::Ci::Config::Entry::Includes do
let(:entry) { described_class.new(config) }
shared_examples 'valid external file' do
it 'should be valid' do
expect(entry).to be_valid
end
it 'should not return any error' do
expect(entry.errors).to be_empty
end
end
shared_examples 'invalid external file' do
it 'should not be valid' do
expect(entry).not_to be_valid
end
it 'should return an error' do
expect(entry.errors.first).to match(/should be a valid local or remote file/)
end
end
describe "#valid?" do
context 'with no external file given' do
let(:config) { nil }
it_behaves_like 'valid external file'
end
context 'with multiple external files' do
let(:config) { %w(https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-2.yml) }
it_behaves_like 'valid external file'
end
context 'with just one external file' do
let(:config) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
it_behaves_like 'valid external file'
end
context 'when they contain valid URLs' do
let(:config) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
it_behaves_like 'valid external file'
end
context 'when they contain valid relative URLs' do
let(:config) { '/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml' }
it_behaves_like 'valid external file'
end
context 'when they not contain valid URLs' do
let(:config) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
it_behaves_like 'invalid external file'
end
context 'when they not contain valid relative URLs' do
let(:config) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
it_behaves_like 'invalid external file'
end
end
describe "#value" do
context 'with multiple external files' do
let(:config) { %w(https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-2.yml) }
it 'should return an array' do
expect(entry.value).to be_an(Array)
expect(entry.value.count).to eq(2)
end
end
context 'with just one external file' do
let(:config) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
it 'should return an array' do
expect(entry.value).to be_an(Array)
expect(entry.value.count).to eq(1)
end
end
context 'with no external file given' do
let(:config) { nil }
it 'should return an empty array' do
expect(entry.value).to be_an(Array)
expect(entry.value).to be_empty
end
end
end
end
...@@ -124,4 +124,61 @@ describe Gitlab::Ci::Config do ...@@ -124,4 +124,61 @@ describe Gitlab::Ci::Config do
end end
end end
end end
context "when yml has valid 'includes' defined" do
let(:yml) do
<<-EOS
includes:
- /spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml
- /spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-2.yml
- https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml
image: ruby:2.2
EOS
end
before do
allow_any_instance_of(Kernel).to receive_message_chain(:open, :read).and_return(yml)
end
it 'should return a composed hash' do
before_script_values = [
"apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", "ruby -v",
"which ruby",
"gem install bundler --no-ri --no-rdoc",
"bundle install --jobs $(nproc) \"${FLAGS[@]}\""
]
variables = {
AUTO_DEVOPS_DOMAIN: "domain.example.com",
POSTGRES_USER: "user",
POSTGRES_PASSWORD: "testing-password",
POSTGRES_ENABLED: "true",
POSTGRES_DB: "$CI_ENVIRONMENT_SLUG"
}
composed_hash = {
before_script: before_script_values,
image: "ruby:2.2",
rspec: { script: ["bundle exec rspec"] },
variables: variables
}
expect(config.to_hash).to eq(composed_hash)
end
end
context "when config has invalid 'includes' defined" do
let(:yml) do
<<-EOS
includes: invalid
EOS
end
it 'raises error' do
expect { config }.to raise_error(
::Gitlab::Ci::ExternalFiles::Processor::ExternalFileError,
/External files should be a valid local or remote file/
)
end
end
end end
...@@ -39,7 +39,7 @@ describe Gitlab::Ci::ExternalFiles::ExternalFile do ...@@ -39,7 +39,7 @@ describe Gitlab::Ci::ExternalFiles::ExternalFile do
end end
describe "#content" do describe "#content" do
let(:external_file_content) { let(:external_file_content) do
<<-HEREDOC <<-HEREDOC
before_script: before_script:
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
...@@ -48,7 +48,7 @@ describe Gitlab::Ci::ExternalFiles::ExternalFile do ...@@ -48,7 +48,7 @@ describe Gitlab::Ci::ExternalFiles::ExternalFile do
- gem install bundler --no-ri --no-rdoc - gem install bundler --no-ri --no-rdoc
- bundle install --jobs $(nproc) "${FLAGS[@]}" - bundle install --jobs $(nproc) "${FLAGS[@]}"
HEREDOC HEREDOC
} end
context 'with a local file' do context 'with a local file' do
let(:value) { '/vendor/gitlab-ci-yml/non-existent-file.yml' } let(:value) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
......
...@@ -3,7 +3,7 @@ require 'rails_helper' ...@@ -3,7 +3,7 @@ require 'rails_helper'
describe Gitlab::Ci::ExternalFiles::Mapper do describe Gitlab::Ci::ExternalFiles::Mapper do
describe '.fetch_paths' do describe '.fetch_paths' do
context 'when includes is defined as string' do context 'when includes is defined as string' do
let(:values) { { includes: '/vendor/gitlab-ci-yml/non-existent-file.yml', image: 'ruby:2.2'} } let(:values) { { includes: '/vendor/gitlab-ci-yml/non-existent-file.yml', image: 'ruby:2.2' } }
it 'returns an array' do it 'returns an array' do
expect(described_class.fetch_paths(values)).to be_an(Array) expect(described_class.fetch_paths(values)).to be_an(Array)
...@@ -15,21 +15,19 @@ describe Gitlab::Ci::ExternalFiles::Mapper do ...@@ -15,21 +15,19 @@ describe Gitlab::Ci::ExternalFiles::Mapper do
end end
context 'when includes is defined as an array' do context 'when includes is defined as an array' do
let(:values) { { includes: ['https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml', '/vendor/gitlab-ci-yml/template.yml'], image: 'ruby:2.2'} } let(:values) { { includes: ['https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml', '/vendor/gitlab-ci-yml/template.yml'], image: 'ruby:2.2' } }
it 'returns an array' do it 'returns an array' do
expect(described_class.fetch_paths(values)).to be_an(Array) expect(described_class.fetch_paths(values)).to be_an(Array)
end end
it 'returns ExternalFile instances' do it 'returns ExternalFile instances' do
paths = described_class.fetch_paths(values) paths = described_class.fetch_paths(values)
paths.each do |path| expect(paths).to all(be_an_instance_of(::Gitlab::Ci::ExternalFiles::ExternalFile))
expect(path).to be_an_instance_of(::Gitlab::Ci::ExternalFiles::ExternalFile)
end
end end
end end
context 'when includes is not defined' do context 'when includes is not defined' do
let(:values) { { image: 'ruby:2.2'} } let(:values) { { image: 'ruby:2.2' } }
it 'returns an empty array' do it 'returns an empty array' do
expect(described_class.fetch_paths(values)).to be_empty expect(described_class.fetch_paths(values)).to be_empty
......
...@@ -13,7 +13,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -13,7 +13,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
end end
context 'when an invalid local file is defined' do context 'when an invalid local file is defined' do
let(:values) { { includes: '/vendor/gitlab-ci-yml/non-existent-file.yml', image: 'ruby:2.2'} } let(:values) { { includes: '/vendor/gitlab-ci-yml/non-existent-file.yml', image: 'ruby:2.2' } }
it 'should raise an error' do it 'should raise an error' do
expect { processor.perform }.to raise_error(described_class::ExternalFileError) expect { processor.perform }.to raise_error(described_class::ExternalFileError)
...@@ -21,7 +21,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -21,7 +21,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
end end
context 'when an invalid remote file is defined' do context 'when an invalid remote file is defined' do
let(:values) { { includes: 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml', image: 'ruby:2.2'} } let(:values) { { includes: 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml', image: 'ruby:2.2' } }
it 'should raise an error' do it 'should raise an error' do
expect { processor.perform }.to raise_error(described_class::ExternalFileError) expect { processor.perform }.to raise_error(described_class::ExternalFileError)
...@@ -30,7 +30,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -30,7 +30,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
context 'with a valid remote external file is defined' do context 'with a valid remote external file is defined' do
let(:values) { { includes: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml', image: 'ruby:2.2' } } let(:values) { { includes: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml', image: 'ruby:2.2' } }
let(:external_file_content) { let(:external_file_content) do
<<-HEREDOC <<-HEREDOC
before_script: before_script:
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
...@@ -47,7 +47,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -47,7 +47,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
script: script:
- bundle exec rubocop - bundle exec rubocop
HEREDOC HEREDOC
} end
before do before do
allow_any_instance_of(Kernel).to receive_message_chain(:open, :read).and_return(external_file_content) allow_any_instance_of(Kernel).to receive_message_chain(:open, :read).and_return(external_file_content)
...@@ -64,8 +64,8 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -64,8 +64,8 @@ describe Gitlab::Ci::ExternalFiles::Processor do
end end
context 'with a valid local external file is defined' do context 'with a valid local external file is defined' do
let(:values) { { includes: '/vendor/gitlab-ci-yml/template.yml' , image: 'ruby:2.2'} } let(:values) { { includes: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } }
let(:external_file_content) { let(:external_file_content) do
<<-HEREDOC <<-HEREDOC
before_script: before_script:
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
...@@ -74,7 +74,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -74,7 +74,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
- gem install bundler --no-ri --no-rdoc - gem install bundler --no-ri --no-rdoc
- bundle install --jobs $(nproc) "${FLAGS[@]}" - bundle install --jobs $(nproc) "${FLAGS[@]}"
HEREDOC HEREDOC
} end
before do before do
allow(File).to receive(:exists?).and_return(true) allow(File).to receive(:exists?).and_return(true)
...@@ -92,23 +92,23 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -92,23 +92,23 @@ describe Gitlab::Ci::ExternalFiles::Processor do
end end
context 'with multiple external files are defined' do context 'with multiple external files are defined' do
let(:external_files) { let(:external_files) do
[ [
"/spec/ee/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml", "/spec/ee/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml",
"/spec/ee/fixtures/gitlab/ci/external_files/.gitlab-ci-template-2.yml", "/spec/ee/fixtures/gitlab/ci/external_files/.gitlab-ci-template-2.yml",
'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml'
] ]
} end
let(:values) { { includes: external_files, image: 'ruby:2.2'} } let(:values) { { includes: external_files, image: 'ruby:2.2' } }
let(:remote_file_content) { let(:remote_file_content) do
<<-HEREDOC <<-HEREDOC
stages: stages:
- build - build
- review - review
- cleanup - cleanup
HEREDOC HEREDOC
} end
before do before do
allow_any_instance_of(Kernel).to receive_message_chain(:open, :read).and_return(remote_file_content) allow_any_instance_of(Kernel).to receive_message_chain(:open, :read).and_return(remote_file_content)
...@@ -124,7 +124,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -124,7 +124,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
end end
context 'when external files are defined but not valid' do context 'when external files are defined but not valid' do
let(:values) { { includes: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2'} } let(:values) { { includes: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } }
let(:external_file_content) { 'invalid content file ////' } let(:external_file_content) { 'invalid content file ////' }
......
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