Commit 3149b5cf authored by Matija Čupić's avatar Matija Čupić

Improve external architecture

CE mirror of 4f17c7b2c30188302e6a73421acbf5a09fb2c823
parent 1a53f017
...@@ -10,16 +10,16 @@ module BlobViewer ...@@ -10,16 +10,16 @@ module BlobViewer
self.file_types = %i(gitlab_ci) self.file_types = %i(gitlab_ci)
self.binary = false self.binary = false
def validation_message def validation_message(project, ref)
return @validation_message if defined?(@validation_message) return @validation_message if defined?(@validation_message)
prepare! prepare!
@validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data, project) @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data, { project: project, branch_name: ref })
end end
def valid? def valid?(project, ref)
validation_message.blank? validation_message(project, ref).blank?
end end
end end
end end
...@@ -464,7 +464,7 @@ module Ci ...@@ -464,7 +464,7 @@ module Ci
return @config_processor if defined?(@config_processor) return @config_processor if defined?(@config_processor)
@config_processor ||= begin @config_processor ||= begin
Gitlab::Ci::YamlProcessor.new(ci_yaml_file, project) initialize_yaml_processor
rescue Gitlab::Ci::YamlProcessor::ValidationError => e rescue Gitlab::Ci::YamlProcessor::ValidationError => e
self.yaml_errors = e.message self.yaml_errors = e.message
nil nil
...@@ -474,6 +474,10 @@ module Ci ...@@ -474,6 +474,10 @@ module Ci
end end
end end
def initialize_yaml_processor
Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, branch_name: ref })
end
def ci_yaml_file_path def ci_yaml_file_path
if project.ci_config_path.blank? if project.ci_config_path.blank?
'.gitlab-ci.yml' '.gitlab-ci.yml'
......
- if viewer.valid? - if viewer.valid?(@project, @ref)
= icon('check fw') = icon('check fw')
This GitLab CI configuration is valid. This GitLab CI configuration is valid.
- else - else
= icon('warning fw') = icon('warning fw')
This GitLab CI configuration is invalid: This GitLab CI configuration is invalid:
= viewer.validation_message = viewer.validation_message(@project, @ref)
= link_to 'Learn more', help_page_path('ci/yaml/README') = link_to 'Learn more', help_page_path('ci/yaml/README')
...@@ -981,6 +981,7 @@ before_script: ...@@ -981,6 +981,7 @@ before_script:
``` ```
```yaml ```yaml
# Content of gitlab-ci.yml
include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-before-script-template.yml' include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-before-script-template.yml'
rspec: rspec:
...@@ -992,13 +993,13 @@ rubocop: ...@@ -992,13 +993,13 @@ rubocop:
- bundle exec rubocop - bundle exec rubocop
``` ```
In the above example `.gitlab-ci-before-script-template.yml` content will be automatically fetched and evaluated as it were a single local file. In the above example `.gitlab-ci-before-script-template.yml` content will be automatically fetched and evaluated along with the content of `gitlab-ci.yml`.
`include` supports two types of files: `include` supports two types of files:
- **local** to the same repository, referenced using the relative path, or - **local** to the same repository, referenced using the relative path, or
- **remote** in a different location, accessed using HTTP(S) protocol, referenced using the full URL - **remote** in a different location, accessed using HTTP protocol, referenced using the full URL
Also, `include` supports a single string or an array of different values, so Also, `include` supports a single string or an array composed by different values, so
```yaml ```yaml
include: '/templates/.gitlab-ci-templates.yml' include: '/templates/.gitlab-ci-templates.yml'
...@@ -1016,11 +1017,12 @@ are both valid use cases. ...@@ -1016,11 +1017,12 @@ are both valid use cases.
#### Restrictions #### Restrictions
- We can only use files that are currently tracked by Git on the default repository branch, so when using a **local file** make sure it's on the latest commit of your default branch, otherwise feel free to use a remote location. - We can only use files that are currently tracked by Git on the same branch and commit your configuration file is. In other words, when using a **local file** make sure it's on the latest commit of your branch.
- Since external files defined on `include` are evaluated first, the configuration on this files will take precedence over the content of `.gitlab-ci.yml`, for example: - Since external files defined on `include` are evaluated first, the content on `gitlab-ci.yml` **will take precedence over the content of the external files**, for example:
```yaml ```yaml
# Content of http://company.com/default-gitlab-ci.yml # Content of http://company.com/default-gitlab-ci.yml
image: php:5-fpm-alpine image: php:5-fpm-alpine
job2: job2:
...@@ -1029,6 +1031,8 @@ job2: ...@@ -1029,6 +1031,8 @@ job2:
```yaml ```yaml
# Content of gitlab-ci.yml
include: include:
- http://company.com/default-gitlab-ci.yml - http://company.com/default-gitlab-ci.yml
...@@ -1038,7 +1042,7 @@ job1: ...@@ -1038,7 +1042,7 @@ job1:
script: ruby -v script: ruby -v
``` ```
In this case both, `job1` and `job2` will be executed with `php:5-fpm-alpine` In this case both, `job1` and `job2` will be executed with `ruby:2.1`
......
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
class Config class Config
ConfigError = Class.new(StandardError) ConfigError = Class.new(StandardError)
def initialize(config, project = nil, opts = {}) def initialize(config, opts = {})
@config = Config::Extendable @config = Config::Extendable
.new(build_config(config, opts)) .new(build_config(config, opts))
.to_hash .to_hash
...@@ -64,17 +64,21 @@ module Gitlab ...@@ -64,17 +64,21 @@ module Gitlab
@global.jobs_value @global.jobs_value
end end
# 'opts' argument is used in EE see /ee/lib/ee/gitlab/ci/config.rb def build_config(config, opts = {})
def build_config(config, project, opts = {})
initial_config = Loader.new(config).load! initial_config = Loader.new(config).load!
project = opts.fetch(:project, nil)
if project.present? if project
processor = ::Gitlab::Ci::ExternalFiles::Processor.new(initial_config, project) process_external_files(initial_config, project, opts)
processor.perform
else else
initial_config initial_config
end end
end end
def process_external_files(config, project, opts)
branch_name = opts.fetch(:branch_name, project.default_branch)
::Gitlab::Ci::External::Processor.new(config, project, branch_name).perform
end
end end
end end
end end
module Gitlab
module Ci
module External
module File
class Local
attr_reader :value, :project, :branch_name
def initialize(value, project, branch_name)
@value = value
@project = project
@branch_name = branch_name
end
def valid?
commit && local_file_content
end
def content
local_file_content
end
private
def commit
@commit ||= project.repository.commit(branch_name)
end
def local_file_content
@local_file_content ||= project.repository.blob_data_at(commit.sha, value)
end
end
end
end
end
end
module Gitlab
module Ci
module External
module File
class Remote
attr_reader :value
def initialize(value)
@value = value
end
def valid?
::Gitlab::UrlSanitizer.valid?(value) && content
end
def content
HTTParty.get(value)
rescue HTTParty::Error, Timeout::Error
false
end
end
end
end
end
end
module Gitlab module Gitlab
module Ci module Ci
module ExternalFiles module External
class Mapper class Mapper
def initialize(values, project) def initialize(values, project, branch_name)
@paths = values.fetch(:include, []) @paths = Array(values.fetch(:include, []))
@project = project @project = project
@branch_name = branch_name
end end
def process def process
if paths.is_a?(String) paths.map { |path| build_external_file(path) }
[build_external_file(paths)]
else
paths.map { |path| build_external_file(path) }
end
end end
private private
attr_reader :paths, :project attr_reader :paths, :project, :branch_name
def build_external_file(path) def build_external_file(path)
::Gitlab::Ci::ExternalFiles::ExternalFile.new(path, project) remote_file = Gitlab::Ci::External::File::Remote.new(path)
if remote_file.valid?
remote_file
else
::Gitlab::Ci::External::File::Local.new(path, project, branch_name)
end
end end
end end
end end
......
module Gitlab module Gitlab
module Ci module Ci
module ExternalFiles module External
class Processor class Processor
ExternalFileError = Class.new(StandardError) FileError = Class.new(StandardError)
def initialize(values, project) def initialize(values, project, branch_name)
@values = values @values = values
@external_files = ::Gitlab::Ci::ExternalFiles::Mapper.new(values, project).process @external_files = ::Gitlab::Ci::External::Mapper.new(values, project, branch_name).process
@content = {}
end end
def perform def perform
...@@ -14,30 +15,34 @@ module Gitlab ...@@ -14,30 +15,34 @@ module Gitlab
external_files.each do |external_file| external_files.each do |external_file|
validate_external_file(external_file) validate_external_file(external_file)
append_external_content(external_file) @content.merge!(content_of(external_file))
end end
append_external_content
remove_include_keyword remove_include_keyword
end end
private private
attr_reader :values, :external_files attr_reader :values, :external_files, :content
def validate_external_file(external_file) def validate_external_file(external_file)
unless external_file.valid? unless external_file.valid?
raise ExternalFileError, "External file: '#{external_file.value}' should be a valid local or remote file" raise FileError, "External file: '#{external_file.value}' should be a valid local or remote file"
end end
end end
def append_external_content(external_file) def content_of(external_file)
external_values = ::Gitlab::Ci::Config::Loader.new(external_file.content).load! ::Gitlab::Ci::Config::Loader.new(external_file.content).load!
@values.merge!(external_values) end
def append_external_content
@content.merge!(@values)
end end
def remove_include_keyword def remove_include_keyword
values.delete(:include) content.delete(:include)
values content
end end
end end
end end
......
module Gitlab
module Ci
module ExternalFiles
class ExternalFile
attr_reader :value, :project
def initialize(value, project)
@value = value
@project = project
end
def content
if remote_url?
HTTParty.get(value)
else
local_file_content
end
rescue HTTParty::Error, Timeout::Error
nil
end
def valid?
remote_url? || local_file_content
end
private
def remote_url?
::Gitlab::UrlSanitizer.valid?(value)
end
def local_file_content
project.repository.blob_data_at(sha, value)
end
def sha
@sha ||= project.repository.commit.sha
end
end
end
end
end
...@@ -7,8 +7,8 @@ module Gitlab ...@@ -7,8 +7,8 @@ module Gitlab
attr_reader :cache, :stages, :jobs attr_reader :cache, :stages, :jobs
def initialize(config, project = nil, opts = {}) def initialize(config, opts = {})
@ci_config = Gitlab::Ci::Config.new(config, project, opts) @ci_config = Gitlab::Ci::Config.new(config, opts)
@config = @ci_config.to_hash @config = @ci_config.to_hash
unless @ci_config.valid? unless @ci_config.valid?
...@@ -73,11 +73,11 @@ module Gitlab ...@@ -73,11 +73,11 @@ module Gitlab
end end
end end
def self.validation_message(content, project = nil, opts = {}) def self.validation_message(content, opts = {})
return 'Please provide content of .gitlab-ci.yml' if content.blank? return 'Please provide content of .gitlab-ci.yml' if content.blank?
begin begin
Gitlab::Ci::YamlProcessor.new(content, project, opts) Gitlab::Ci::YamlProcessor.new(content, opts)
nil nil
rescue ValidationError, ::Gitlab::Ci::ExternalFiles::Processor::ExternalFileError => e rescue ValidationError, ::Gitlab::Ci::ExternalFiles::Processor::ExternalFileError => e
e.message e.message
......
...@@ -5,11 +5,11 @@ require_dependency 'active_model' ...@@ -5,11 +5,11 @@ require_dependency 'active_model'
describe Gitlab::Ci::Config do describe Gitlab::Ci::Config do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:config) do let(:config) do
described_class.new(yml, project) described_class.new(gitlab_ci_yml, { project: project, branch_name: 'testing' })
end end
context 'when config is valid' do context 'when config is valid' do
let(:yml) do let(:gitlab_ci_yml) do
<<-EOS <<-EOS
image: ruby:2.2 image: ruby:2.2
...@@ -126,7 +126,7 @@ describe Gitlab::Ci::Config do ...@@ -126,7 +126,7 @@ describe Gitlab::Ci::Config do
end end
end end
context "when yml has valid 'include' defined" do context "when gitlab_ci_yml has valid 'include' defined" do
let(:http_file_content) do let(:http_file_content) do
<<~HEREDOC <<~HEREDOC
variables: variables:
...@@ -149,7 +149,8 @@ describe Gitlab::Ci::Config do ...@@ -149,7 +149,8 @@ describe Gitlab::Ci::Config do
end end
before do before do
allow_any_instance_of(::Gitlab::Ci::ExternalFiles::ExternalFile).to receive(:local_file_content).and_return(local_file_content) allow_any_instance_of(::Gitlab::Ci::External::File::Local).to receive(:commit).and_return('12345')
allow_any_instance_of(::Gitlab::Ci::External::File::Local).to receive(:local_file_content).and_return(local_file_content)
allow(HTTParty).to receive(:get).and_return(http_file_content) allow(HTTParty).to receive(:get).and_return(http_file_content)
end end
...@@ -178,8 +179,8 @@ describe Gitlab::Ci::Config do ...@@ -178,8 +179,8 @@ describe Gitlab::Ci::Config do
end end
end end
context "when yml has invalid 'include' defined" do context "when gitlab_ci.yml has invalid 'include' defined" do
let(:yml) do let(:gitlab_ci_yml) do
<<-EOS <<-EOS
include: invalid include: invalid
EOS EOS
...@@ -193,11 +194,11 @@ describe Gitlab::Ci::Config do ...@@ -193,11 +194,11 @@ describe Gitlab::Ci::Config do
end end
end end
context "when both external files and gitlab_ci defined the same key" do context "when both external files and gitlab_ci.yml defined the same key" do
let(:yml) do let(:gitlab_ci_yml) do
<<~HEREDOC <<~HEREDOC
include: include:
- 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.gitlab_ci_yml
image: ruby:2.2 image: ruby:2.2
HEREDOC HEREDOC
...@@ -211,7 +212,7 @@ describe Gitlab::Ci::Config do ...@@ -211,7 +212,7 @@ describe Gitlab::Ci::Config do
it 'should take precedence' do it 'should take precedence' do
allow(HTTParty).to receive(:get).and_return(http_file_content) allow(HTTParty).to receive(:get).and_return(http_file_content)
expect(config.to_hash).to eq({ image: 'php:5-fpm-alpine' }) expect(config.to_hash).to eq({ image: 'ruby:2.2' })
end end
end end
end end
require 'fast_spec_helper'
describe Gitlab::Ci::External::File::Local do
let(:project) { create(:project, :repository) }
let(:local_file) { described_class.new(value, project, 'testing') }
describe "#valid?" do
context 'when is a valid local path' do
let(:value) { '/vendor/gitlab-ci-yml/existent-file.yml' }
before do
allow_any_instance_of(described_class).to receive(:commit).and_return('12345')
allow_any_instance_of(described_class).to receive(:local_file_content).and_return("image: 'ruby2:2'")
end
it 'should return true' do
expect(local_file.valid?).to be_truthy
end
end
context 'when is not a valid local path' do
let(:value) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
it 'should return false' do
expect(local_file.valid?).to be_falsy
end
end
describe "#content" do
let(:local_file_content) do
<<~HEREDOC
before_script:
- 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[@]}"
HEREDOC
end
context 'with a local file' do
let(:value) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
before do
allow_any_instance_of(described_class).to receive(:local_file_content).and_return(local_file_content)
end
it 'should return the content of the file' do
expect(local_file.content).to eq(local_file_content)
end
end
end
end
end
require 'fast_spec_helper'
describe Gitlab::Ci::External::File::Remote do
let(:remote_file) { described_class.new(value) }
let(:value) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:remote_file_content) do
<<~HEREDOC
before_script:
- 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[@]}"
HEREDOC
end
describe "#valid?" do
context 'when is a valid remote url' do
before do
allow(HTTParty).to receive(:get).and_return(remote_file_content)
end
it 'should return true' do
expect(remote_file.valid?).to be_truthy
end
end
context 'when is not a valid remote url' do
let(:value) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
it 'should return false' do
expect(remote_file.valid?).to be_falsy
end
end
context 'with a timeout' do
before do
allow(HTTParty).to receive(:get).and_raise(Timeout::Error)
end
it 'should be falsy' do
expect(remote_file.valid?).to be_falsy
end
end
end
describe "#content" do
context 'with a valid remote file' do
before do
allow(HTTParty).to receive(:get).and_return(remote_file_content)
end
it 'should return the content of the file' do
expect(remote_file.content).to eq(remote_file_content)
end
end
context 'with a timeout' do
before do
allow(HTTParty).to receive(:get).and_raise(Timeout::Error)
end
it 'should be falsy' do
expect(remote_file.content).to be_falsy
end
end
end
end
require 'fast_spec_helper' require 'fast_spec_helper'
describe Gitlab::Ci::ExternalFiles::Mapper do describe Gitlab::Ci::External::Mapper do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:file_content) do
<<~HEREDOC
image: 'ruby:2.2'
HEREDOC
end
describe '#process' do describe '#process' do
subject { described_class.new(values, project).process } subject { described_class.new(values, project, 'testing').process }
context "when 'include' keyword is defined as string" do context "when 'include' keyword is defined as string" do
let(:values) do context 'when the string is a local file' do
{ let(:values) do
include: '/vendor/gitlab-ci-yml/non-existent-file.yml', {
image: 'ruby:2.2' include: '/vendor/gitlab-ci-yml/non-existent-file.yml',
} image: 'ruby:2.2'
end }
end
it 'returns an array' do it 'returns an array' do
expect(subject).to be_an(Array) expect(subject).to be_an(Array)
end
it 'returns File instances' do
expect(subject.first).to be_an_instance_of(::Gitlab::Ci::External::File::Local)
end
end end
it 'returns ExternalFile instances' do context 'when the string is a remote file' do
expect(subject.first).to be_an_instance_of(::Gitlab::Ci::ExternalFiles::ExternalFile) let(:values) do
{
include: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml',
image: 'ruby:2.2'
}
end
before do
allow(HTTParty).to receive(:get).and_return(file_content)
end
it 'returns an array' do
expect(subject).to be_an(Array)
end
it 'returns File instances' do
expect(subject.first).to be_an_instance_of(::Gitlab::Ci::External::File::Remote)
end
end end
end end
...@@ -35,18 +63,25 @@ describe Gitlab::Ci::ExternalFiles::Mapper do ...@@ -35,18 +63,25 @@ describe Gitlab::Ci::ExternalFiles::Mapper do
} }
end end
before do
allow(HTTParty).to receive(:get).and_return(file_content)
end
it 'returns an array' do it 'returns an array' do
expect(subject).to be_an(Array) expect(subject).to be_an(Array)
end end
it 'returns ExternalFile instances' do it 'returns Files instances' do
expect(subject).to all(be_an_instance_of(::Gitlab::Ci::ExternalFiles::ExternalFile)) expect(subject).to all(respond_to(:valid?))
expect(subject).to all(respond_to(:content))
end end
end end
context "when 'include' is not defined" do context "when 'include' is not defined" do
let(:values) do let(:values) do
{ image: 'ruby:2.2' } {
image: 'ruby:2.2'
}
end end
it 'returns an empty array' do it 'returns an empty array' do
......
require 'fast_spec_helper' require 'fast_spec_helper'
describe Gitlab::Ci::ExternalFiles::Processor do describe Gitlab::Ci::External::Processor do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:processor) { described_class.new(values, project) } let(:processor) { described_class.new(values, project, 'testing') }
describe "#perform" do describe "#perform" do
context 'when no external files defined' do context 'when no external files defined' do
...@@ -18,7 +18,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -18,7 +18,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
it 'should raise an error' do it 'should raise an error' do
expect { processor.perform }.to raise_error( expect { processor.perform }.to raise_error(
described_class::ExternalFileError, described_class::FileError,
"External file: '/vendor/gitlab-ci-yml/non-existent-file.yml' should be a valid local or remote file" "External file: '/vendor/gitlab-ci-yml/non-existent-file.yml' should be a valid local or remote file"
) )
end end
...@@ -29,7 +29,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -29,7 +29,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
it 'should raise an error' do it 'should raise an error' do
expect { processor.perform }.to raise_error( expect { processor.perform }.to raise_error(
described_class::ExternalFileError, described_class::FileError,
"External file: 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' should be a valid local or remote file" "External file: 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' should be a valid local or remote file"
) )
end end
...@@ -72,7 +72,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -72,7 +72,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
context 'with a valid local external file is defined' do context 'with a valid local external file is defined' do
let(:values) { { include: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } } let(:values) { { include: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } }
let(:external_file_content) do let(:local_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
...@@ -84,7 +84,8 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -84,7 +84,8 @@ describe Gitlab::Ci::ExternalFiles::Processor do
end end
before do before do
allow_any_instance_of(::Gitlab::Ci::ExternalFiles::ExternalFile).to receive(:local_file_content).and_return(external_file_content) allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:commit).and_return('12345')
allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:local_file_content).and_return(local_file_content)
end end
it 'should append the file to the values' do it 'should append the file to the values' do
...@@ -93,7 +94,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -93,7 +94,7 @@ describe Gitlab::Ci::ExternalFiles::Processor do
end end
it "should remove the 'include' keyword" do it "should remove the 'include' keyword" do
expect(processor.perform[:includes]).to be_nil expect(processor.perform[:include]).to be_nil
end end
end end
...@@ -104,7 +105,12 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -104,7 +105,12 @@ describe Gitlab::Ci::ExternalFiles::Processor do
'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 end
let(:values) { { include: external_files, image: 'ruby:2.2' } } let(:values) do
{
include: external_files,
image: 'ruby:2.2'
}
end
let(:remote_file_content) do let(:remote_file_content) do
<<-HEREDOC <<-HEREDOC
...@@ -116,8 +122,9 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -116,8 +122,9 @@ describe Gitlab::Ci::ExternalFiles::Processor do
end end
before do before do
file_content = File.read("#{Rails.root}/spec/ee/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml") local_file_content = File.read("#{Rails.root}/spec/ee/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml")
allow_any_instance_of(::Gitlab::Ci::ExternalFiles::ExternalFile).to receive(:local_file_content).and_return(file_content) allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:commit).and_return('12345')
allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:local_file_content).and_return(local_file_content)
allow(HTTParty).to receive(:get).and_return(remote_file_content) allow(HTTParty).to receive(:get).and_return(remote_file_content)
end end
...@@ -133,15 +140,36 @@ describe Gitlab::Ci::ExternalFiles::Processor do ...@@ -133,15 +140,36 @@ describe Gitlab::Ci::ExternalFiles::Processor do
context 'when external files are defined but not valid' do context 'when external files are defined but not valid' do
let(:values) { { include: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } } let(:values) { { include: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } }
let(:external_file_content) { 'invalid content file ////' } let(:local_file_content) { 'invalid content file ////' }
before do before do
allow_any_instance_of(::Gitlab::Ci::ExternalFiles::ExternalFile).to receive(:local_file_content).and_return(external_file_content) allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:commit).and_return('12345')
allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:local_file_content).and_return(local_file_content)
end end
it 'should raise an error' do it 'should raise an error' do
expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError) expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError)
end end
end end
context "when both external files and values defined the same key" do
let(:values) do
{
include: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml',
image: 'ruby:2.2'
}
end
let(:remote_file_content) do
<<~HEREDOC
image: php:5-fpm-alpine
HEREDOC
end
it 'should take precedence' do
allow(HTTParty).to receive(:get).and_return(remote_file_content)
expect(processor.perform[:image]).to eq('ruby:2.2')
end
end
end end
end end
require 'fast_spec_helper'
describe Gitlab::Ci::ExternalFiles::ExternalFile do
let(:project) { create(:project, :repository) }
let(:external_file) { described_class.new(value, project) }
describe "#valid?" do
context 'when is a valid remote url' do
let(:value) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
before do
allow_any_instance_of(described_class).to receive(:local_file_content).and_return("image: 'ruby2:2'")
end
it 'should return true' do
expect(external_file.valid?).to be_truthy
end
end
context 'when is not a valid remote url' do
let(:value) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
it 'should return false' do
expect(external_file.valid?).to be_falsy
end
end
context 'when is a valid local path' do
let(:value) { '/vendor/gitlab-ci-yml/existent-file.yml' }
it 'should return true' do
allow(File).to receive(:exists?).and_return(true)
expect(external_file.valid?).to be_truthy
end
end
context 'when is not a valid local path' do
let(:value) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
it 'should return false' do
expect(external_file.valid?).to be_falsy
end
end
end
describe "#content" do
let(:external_file_content) do
<<-HEREDOC
before_script:
- 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[@]}"
HEREDOC
end
context 'with a local file' do
let(:value) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
before do
allow_any_instance_of(described_class).to receive(:local_file_content).and_return(external_file_content)
end
it 'should return the content of the file' do
expect(external_file.content).to eq(external_file_content)
end
end
context 'with a valid remote file' do
let(:value) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
before do
allow(HTTParty).to receive(:get).and_return(external_file_content)
end
it 'should return the content of the file' do
expect(external_file.content).to eq(external_file_content)
end
end
context 'with a timeout' do
let(:value) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
before do
allow(HTTParty).to receive(:get).and_raise(Timeout::Error)
end
it 'should return nil' do
expect(external_file.content).to be_nil
end
end
end
end
...@@ -12,12 +12,12 @@ describe BlobViewer::GitlabCiYml do ...@@ -12,12 +12,12 @@ describe BlobViewer::GitlabCiYml do
it 'calls prepare! on the viewer' do it 'calls prepare! on the viewer' do
expect(subject).to receive(:prepare!) expect(subject).to receive(:prepare!)
subject.validation_message subject.validation_message(project, project.default_branch)
end end
context 'when the configuration is valid' do context 'when the configuration is valid' do
it 'returns nil' do it 'returns nil' do
expect(subject.validation_message).to be_nil expect(subject.validation_message(project, project.default_branch)).to be_nil
end end
end end
...@@ -25,7 +25,7 @@ describe BlobViewer::GitlabCiYml do ...@@ -25,7 +25,7 @@ describe BlobViewer::GitlabCiYml do
let(:data) { 'oof' } let(:data) { 'oof' }
it 'returns the error message' do it 'returns the error message' do
expect(subject.validation_message).to eq('Invalid configuration format') expect(subject.validation_message(project, project.default_branch)).to eq('Invalid configuration format')
end end
end end
end end
......
...@@ -1743,7 +1743,7 @@ describe Ci::Pipeline, :mailer do ...@@ -1743,7 +1743,7 @@ describe Ci::Pipeline, :mailer do
create(:ci_pipeline, config: { rspec: { script: 'rake test' } }) create(:ci_pipeline, config: { rspec: { script: 'rake test' } })
end end
it 'does not containyaml errors' do it 'does not contain yaml errors' do
expect(pipeline).not_to have_yaml_errors expect(pipeline).not_to have_yaml_errors
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