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 ...@@ -7,9 +7,16 @@ module Gitlab
# #
class Image < Node class Image < Node
include Validatable include Validatable
include DockerImage
ALLOWED_KEYS = %i[name entrypoint].freeze
validations do 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 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 ...@@ -9,7 +9,30 @@ module Gitlab
include Validatable include Validatable
validations do 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 end
end end
......
...@@ -44,6 +44,14 @@ module Gitlab ...@@ -44,6 +44,14 @@ module Gitlab
end end
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 class KeyValidator < ActiveModel::EachValidator
include LegacyValidationHelpers include LegacyValidationHelpers
......
...@@ -598,8 +598,8 @@ module Ci ...@@ -598,8 +598,8 @@ module Ci
describe "Image and service handling" do describe "Image and service handling" do
it "returns image and service when defined" do it "returns image and service when defined" do
config = YAML.dump({ config = YAML.dump({
image: "ruby:2.1", image: { name: "ruby:2.1" },
services: ["mysql"], services: ["mysql", { name: "docker:dind", alias: "docker" }],
before_script: ["pwd"], before_script: ["pwd"],
rspec: { script: "rspec" } rspec: { script: "rspec" }
}) })
...@@ -615,8 +615,8 @@ module Ci ...@@ -615,8 +615,8 @@ module Ci
coverage_regex: nil, coverage_regex: nil,
tag_list: [], tag_list: [],
options: { options: {
image: "ruby:2.1", image: { name: "ruby:2.1" },
services: ["mysql"] services: [{ name: "mysql" }, { name: "docker:dind", alias: "docker" }]
}, },
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
...@@ -630,7 +630,7 @@ module Ci ...@@ -630,7 +630,7 @@ module Ci
image: "ruby:2.1", image: "ruby:2.1",
services: ["mysql"], services: ["mysql"],
before_script: ["pwd"], 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) config_processor = GitlabCiYamlProcessor.new(config, path)
...@@ -644,8 +644,8 @@ module Ci ...@@ -644,8 +644,8 @@ module Ci
coverage_regex: nil, coverage_regex: nil,
tag_list: [], tag_list: [],
options: { options: {
image: "ruby:2.5", image: { name: "ruby:2.5" },
services: ["postgresql"] services: [{ name: "postgresql" }, { name: "docker:dind" }]
}, },
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
...@@ -884,8 +884,8 @@ module Ci ...@@ -884,8 +884,8 @@ module Ci
coverage_regex: nil, coverage_regex: nil,
tag_list: [], tag_list: [],
options: { options: {
image: "ruby:2.1", image: { name: "ruby:2.1" },
services: ["mysql"], services: [{ name: "mysql" }],
artifacts: { artifacts: {
name: "custom_name", name: "custom_name",
paths: ["logs/", "binaries/"], paths: ["logs/", "binaries/"],
...@@ -1261,7 +1261,7 @@ EOT ...@@ -1261,7 +1261,7 @@ EOT
config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) 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 end
it "returns errors if job name is blank" do it "returns errors if job name is blank" do
...@@ -1282,35 +1282,35 @@ EOT ...@@ -1282,35 +1282,35 @@ EOT
config = YAML.dump({ rspec: { script: "test", image: ["test"] } }) config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) 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 end
it "returns errors if services parameter is not an array" do it "returns errors if services parameter is not an array" do
config = YAML.dump({ services: "test", rspec: { script: "test" } }) config = YAML.dump({ services: "test", rspec: { script: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) 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 end
it "returns errors if services parameter is not an array of strings" do it "returns errors if services parameter is not an array of strings" do
config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } }) config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) 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 end
it "returns errors if job services parameter is not an array" do it "returns errors if job services parameter is not an array" do
config = YAML.dump({ rspec: { script: "test", services: "test" } }) config = YAML.dump({ rspec: { script: "test", services: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) 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 end
it "returns errors if job services parameter is not an array of strings" do it "returns errors if job services parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } }) config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) 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 end
it "returns error if job configuration is invalid" do it "returns error if job configuration is invalid" do
...@@ -1324,7 +1324,7 @@ EOT ...@@ -1324,7 +1324,7 @@ EOT
config = YAML.dump({ extra: { script: 'rspec', services: "test" } }) config = YAML.dump({ extra: { script: 'rspec', services: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) 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 end
it "returns errors if there are no jobs defined" do it "returns errors if there are no jobs defined" do
......
...@@ -95,13 +95,13 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -95,13 +95,13 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#image_value' do describe '#image_value' do
it 'returns valid image' 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
end end
describe '#services_value' do describe '#services_value' do
it 'returns array of services' 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
end end
...@@ -150,8 +150,8 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -150,8 +150,8 @@ describe Gitlab::Ci::Config::Entry::Global do
script: %w[rspec ls], script: %w[rspec ls],
before_script: %w(ls pwd), before_script: %w(ls pwd),
commands: "ls\npwd\nrspec\nls", commands: "ls\npwd\nrspec\nls",
image: 'ruby:2.2', image: { name: 'ruby:2.2' },
services: ['postgres:9.1', 'mysql:5.5'], services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test', stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] }, cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: { 'VAR' => 'value' }, variables: { 'VAR' => 'value' },
...@@ -161,8 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -161,8 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do
before_script: [], before_script: [],
script: %w[spinach], script: %w[spinach],
commands: 'spinach', commands: 'spinach',
image: 'ruby:2.2', image: { name: 'ruby:2.2' },
services: ['postgres:9.1', 'mysql:5.5'], services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test', stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] }, cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: {}, variables: {},
......
...@@ -3,13 +3,12 @@ require 'spec_helper' ...@@ -3,13 +3,12 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Image do describe Gitlab::Ci::Config::Entry::Image do
let(:entry) { described_class.new(config) } let(:entry) { described_class.new(config) }
describe 'validation' do context 'when configuration is a string' do
context 'when entry config value is correct' do
let(:config) { 'ruby:2.2' } let(:config) { 'ruby:2.2' }
describe '#value' do describe '#value' do
it 'returns image string' do it 'returns image hash' do
expect(entry.value).to eq 'ruby:2.2' expect(entry.value).to eq({ name: 'ruby:2.2' })
end end
end end
...@@ -24,6 +23,52 @@ describe Gitlab::Ci::Config::Entry::Image do ...@@ -24,6 +23,52 @@ describe Gitlab::Ci::Config::Entry::Image do
expect(entry).to be_valid expect(entry).to be_valid
end end
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 end
context 'when entry value is not correct' do context 'when entry value is not correct' do
...@@ -32,7 +77,7 @@ describe Gitlab::Ci::Config::Entry::Image do ...@@ -32,7 +77,7 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do describe '#errors' do
it 'saves errors' do it 'saves errors' do
expect(entry.errors) expect(entry.errors)
.to include 'image config should be a string' .to include 'image config should be a hash or a string'
end end
end end
...@@ -42,5 +87,21 @@ describe Gitlab::Ci::Config::Entry::Image do ...@@ -42,5 +87,21 @@ describe Gitlab::Ci::Config::Entry::Image do
end end
end 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
end end
...@@ -104,7 +104,7 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -104,7 +104,7 @@ describe Gitlab::Ci::Config::Entry::Job do
end end
it 'overrides global config' do 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') expect(entry[:cache].value).to eq(key: 'test')
end end
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' ...@@ -3,38 +3,31 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Services do describe Gitlab::Ci::Config::Entry::Services do
let(:entry) { described_class.new(config) } let(:entry) { described_class.new(config) }
describe 'validations' do before { entry.compose! }
context 'when entry config value is correct' do
let(:config) { ['postgres:9.1', 'mysql:5.5'] }
describe '#value' do context 'when configuration is valid' do
it 'returns array of services as is' do let(:config) { ['postgresql:9.5', { name: 'postgresql:9.1', alias: 'postgres_old' }] }
expect(entry.value).to eq config
end
end
describe '#valid?' do describe '#valid?' do
it 'is valid' do it 'is valid' do
expect(entry).to be_valid expect(entry).to be_valid
end end
end end
end
context 'when entry value is not correct' do
let(:config) { 'ls' }
describe '#errors' do describe '#value' do
it 'saves errors' do it 'returns valid array' do
expect(entry.errors) expect(entry.value).to eq([{ name: 'postgresql:9.5' }, { name: 'postgresql:9.1', alias: 'postgres_old' }])
.to include 'services config should be an array of strings' end
end end
end end
context 'when configuration is invalid' do
let(:config) { 'postgresql:9.5' }
describe '#valid?' do describe '#valid?' do
it 'is not valid' do it 'is invalid' do
expect(entry).not_to be_valid expect(entry).not_to be_valid
end end
end end
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