Commit 8c6e2bad authored by Tomasz Maczukin's avatar Tomasz Maczukin

Add support for docker image configuration in .gitlab-ci.yml

parent 5478ff6d
module Gitlab
module Ci
class Config
module Entry
module DockerImage
def hash?
@config.is_a?(Hash)
end
def string?
@config.is_a?(String)
end
def name
value[:name]
end
def entrypoint
value[:entrypoint]
end
def value
return { name: @config } if string?
return @config if hash?
{}
end
end
end
end
end
end
......@@ -7,9 +7,16 @@ module Gitlab
#
class Image < Node
include Validatable
include DockerImage
ALLOWED_KEYS = %i[name entrypoint].freeze
validations do
validates :config, type: String
validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :name, type: String, presence: true
validates :entrypoint, type: String, allow_nil: true
end
end
end
......
module Gitlab
module Ci
class Config
module Entry
##
# Entry that represents a configuration of Docker service.
#
class Service < Node
include Validatable
include DockerImage
ALLOWED_KEYS = %i[name entrypoint command alias].freeze
validations do
validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :name, type: String, presence: true
validates :entrypoint, type: String, allow_nil: true
validates :command, type: String, allow_nil: true
validates :alias, type: String, allow_nil: true
end
def alias
value[:alias]
end
def command
value[:command]
end
end
end
end
end
end
......@@ -9,7 +9,30 @@ module Gitlab
include Validatable
validations do
validates :config, array_of_strings: true
validates :config, type: Array
end
def compose!(deps = nil)
super do
@entries = []
@config.each do |config|
@entries << Entry::Factory.new(Entry::Service)
.value(config || {})
.create!
end
@entries.each do |entry|
entry.compose!(deps)
end
end
end
def value
@entries.map(&:value)
end
def descendants
@entries
end
end
end
......
......@@ -44,6 +44,14 @@ module Gitlab
end
end
class HashOrStringValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.is_a?(Hash) || value.is_a?(String)
record.errors.add(attribute, 'should be a hash or a string')
end
end
end
class KeyValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
......
......@@ -598,8 +598,8 @@ module Ci
describe "Image and service handling" do
it "returns image and service when defined" do
config = YAML.dump({
image: "ruby:2.1",
services: ["mysql"],
image: { name: "ruby:2.1" },
services: ["mysql", { name: "docker:dind", alias: "docker" }],
before_script: ["pwd"],
rspec: { script: "rspec" }
})
......@@ -615,8 +615,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.1",
services: ["mysql"]
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }, { name: "docker:dind", alias: "docker" }]
},
allow_failure: false,
when: "on_success",
......@@ -630,7 +630,7 @@ module Ci
image: "ruby:2.1",
services: ["mysql"],
before_script: ["pwd"],
rspec: { image: "ruby:2.5", services: ["postgresql"], script: "rspec" }
rspec: { image: "ruby:2.5", services: [{ name: "postgresql" }, "docker:dind"], script: "rspec" }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
......@@ -644,8 +644,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.5",
services: ["postgresql"]
image: { name: "ruby:2.5" },
services: [{ name: "postgresql" }, { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
......@@ -884,8 +884,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.1",
services: ["mysql"],
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }],
artifacts: {
name: "custom_name",
paths: ["logs/", "binaries/"],
......@@ -1261,7 +1261,7 @@ EOT
config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image config should be a string")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image config should be a hash or a string")
end
it "returns errors if job name is blank" do
......@@ -1282,35 +1282,35 @@ EOT
config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a string")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a hash or a string")
end
it "returns errors if services parameter is not an array" do
config = YAML.dump({ services: "test", rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be a array")
end
it "returns errors if services parameter is not an array of strings" do
config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "service config should be a hash or a string")
end
it "returns errors if job services parameter is not an array" do
config = YAML.dump({ rspec: { script: "test", services: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be a array")
end
it "returns errors if job services parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "service config should be a hash or a string")
end
it "returns error if job configuration is invalid" do
......@@ -1324,7 +1324,7 @@ EOT
config = YAML.dump({ extra: { script: 'rspec', services: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be a array")
end
it "returns errors if there are no jobs defined" do
......
......@@ -95,13 +95,13 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#image_value' do
it 'returns valid image' do
expect(global.image_value).to eq 'ruby:2.2'
expect(global.image_value).to eq(name: 'ruby:2.2')
end
end
describe '#services_value' do
it 'returns array of services' do
expect(global.services_value).to eq ['postgres:9.1', 'mysql:5.5']
expect(global.services_value).to eq [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }]
end
end
......@@ -150,8 +150,8 @@ describe Gitlab::Ci::Config::Entry::Global do
script: %w[rspec ls],
before_script: %w(ls pwd),
commands: "ls\npwd\nrspec\nls",
image: 'ruby:2.2',
services: ['postgres:9.1', 'mysql:5.5'],
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: { 'VAR' => 'value' },
......@@ -161,8 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do
before_script: [],
script: %w[spinach],
commands: 'spinach',
image: 'ruby:2.2',
services: ['postgres:9.1', 'mysql:5.5'],
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: {},
......
......@@ -3,13 +3,12 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Image do
let(:entry) { described_class.new(config) }
describe 'validation' do
context 'when entry config value is correct' do
context 'when configuration is a string' do
let(:config) { 'ruby:2.2' }
describe '#value' do
it 'returns image string' do
expect(entry.value).to eq 'ruby:2.2'
it 'returns image hash' do
expect(entry.value).to eq({ name: 'ruby:2.2' })
end
end
......@@ -24,6 +23,52 @@ describe Gitlab::Ci::Config::Entry::Image do
expect(entry).to be_valid
end
end
describe '#image' do
it "returns image's name" do
expect(entry.name).to eq 'ruby:2.2'
end
end
describe '#entrypoint' do
it "returns image's entrypoint" do
expect(entry.entrypoint).to be_nil
end
end
end
context 'when configuration is a hash' do
let(:config) { { name: 'ruby:2.2', entrypoint: '/bin/sh' } }
describe '#value' do
it 'returns image hash' do
expect(entry.value).to eq(config)
end
end
describe '#errors' do
it 'does not append errors' do
expect(entry.errors).to be_empty
end
end
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
describe '#image' do
it "returns image's name" do
expect(entry.name).to eq 'ruby:2.2'
end
end
describe '#entrypoint' do
it "returns image's entrypoint" do
expect(entry.entrypoint).to eq '/bin/sh'
end
end
end
context 'when entry value is not correct' do
......@@ -32,7 +77,7 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'image config should be a string'
.to include 'image config should be a hash or a string'
end
end
......@@ -42,5 +87,21 @@ describe Gitlab::Ci::Config::Entry::Image do
end
end
end
context 'when unexpected key is specified' do
let(:config) { { name: 'ruby:2.2', non_existing: 'test' } }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'image config contains unknown keys: non_existing'
end
end
describe '#valid?' do
it 'is not valid' do
expect(entry).not_to be_valid
end
end
end
end
......@@ -104,7 +104,7 @@ describe Gitlab::Ci::Config::Entry::Job do
end
it 'overrides global config' do
expect(entry[:image].value).to eq 'some_image'
expect(entry[:image].value).to eq(name: 'some_image')
expect(entry[:cache].value).to eq(key: 'test')
end
end
......
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Service do
let(:entry) { described_class.new(config) }
before { entry.compose! }
context 'when configuration is a string' do
let(:config) { 'postgresql:9.5' }
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
describe '#value' do
it 'returns valid hash' do
expect(entry.value).to include(name: 'postgresql:9.5')
end
end
describe '#image' do
it "returns service's image name" do
expect(entry.name).to eq 'postgresql:9.5'
end
end
describe '#alias' do
it "returns service's alias" do
expect(entry.alias).to be_nil
end
end
describe '#command' do
it "returns service's command" do
expect(entry.command).to be_nil
end
end
end
context 'when configuration is a hash' do
let(:config) do
{ name: 'postgresql:9.5', alias: 'db', command: 'cmd', entrypoint: '/bin/sh' }
end
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
describe '#value' do
it 'returns valid hash' do
expect(entry.value).to eq config
end
end
describe '#image' do
it "returns service's image name" do
expect(entry.name).to eq 'postgresql:9.5'
end
end
describe '#alias' do
it "returns service's alias" do
expect(entry.alias).to eq 'db'
end
end
describe '#command' do
it "returns service's command" do
expect(entry.command).to eq 'cmd'
end
end
describe '#entrypoint' do
it "returns service's entrypoint" do
expect(entry.entrypoint).to eq '/bin/sh'
end
end
end
context 'when entry value is not correct' do
let(:config) { ['postgresql:9.5'] }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'service config should be a hash or a string'
end
end
describe '#valid?' do
it 'is not valid' do
expect(entry).not_to be_valid
end
end
end
context 'when unexpected key is specified' do
let(:config) { { name: 'postgresql:9.5', non_existing: 'test' } }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'service config contains unknown keys: non_existing'
end
end
describe '#valid?' do
it 'is not valid' do
expect(entry).not_to be_valid
end
end
end
end
......@@ -3,38 +3,31 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Services do
let(:entry) { described_class.new(config) }
describe 'validations' do
context 'when entry config value is correct' do
let(:config) { ['postgres:9.1', 'mysql:5.5'] }
before { entry.compose! }
describe '#value' do
it 'returns array of services as is' do
expect(entry.value).to eq config
end
end
context 'when configuration is valid' do
let(:config) { ['postgresql:9.5', { name: 'postgresql:9.1', alias: 'postgres_old' }] }
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
end
context 'when entry value is not correct' do
let(:config) { 'ls' }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'services config should be an array of strings'
describe '#value' do
it 'returns valid array' do
expect(entry.value).to eq([{ name: 'postgresql:9.5' }, { name: 'postgresql:9.1', alias: 'postgres_old' }])
end
end
end
context 'when configuration is invalid' do
let(:config) { 'postgresql:9.5' }
describe '#valid?' do
it 'is not valid' do
it 'is invalid' do
expect(entry).not_to be_valid
end
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