Merge branch 'feature/add-support-for-services-configuration' into 'master'
Add support for services configuration in .gitlab-ci.yml See merge request !8578
This commit is contained in:
commit
b29bf62602
18 changed files with 548 additions and 140 deletions
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Add support for image and services configuration in .gitlab-ci.yml
|
||||||
|
merge_request: 8578
|
||||||
|
author:
|
|
@ -804,7 +804,11 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
class Image < Grape::Entity
|
class Image < Grape::Entity
|
||||||
expose :name
|
expose :name, :entrypoint
|
||||||
|
end
|
||||||
|
|
||||||
|
class Service < Image
|
||||||
|
expose :alias, :command
|
||||||
end
|
end
|
||||||
|
|
||||||
class Artifacts < Grape::Entity
|
class Artifacts < Grape::Entity
|
||||||
|
@ -848,7 +852,7 @@ module API
|
||||||
expose :variables
|
expose :variables
|
||||||
expose :steps, using: Step
|
expose :steps, using: Step
|
||||||
expose :image, using: Image
|
expose :image, using: Image
|
||||||
expose :services, using: Image
|
expose :services, using: Service
|
||||||
expose :artifacts, using: Artifacts
|
expose :artifacts, using: Artifacts
|
||||||
expose :cache, using: Cache
|
expose :cache, using: Cache
|
||||||
expose :credentials, using: Credentials
|
expose :credentials, using: Credentials
|
||||||
|
|
|
@ -45,7 +45,21 @@ module Ci
|
||||||
expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? }
|
expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? }
|
||||||
|
|
||||||
expose :options do |model|
|
expose :options do |model|
|
||||||
model.options
|
# This part ensures that output of old API is still the same after adding support
|
||||||
|
# for extended docker configuration options, used by new API
|
||||||
|
#
|
||||||
|
# I'm leaving this here, not in the model, because it should be removed at the same time
|
||||||
|
# when old API will be removed (planned for August 2017).
|
||||||
|
model.options.dup.tap do |options|
|
||||||
|
options[:image] = options[:image][:name] if options[:image].is_a?(Hash)
|
||||||
|
options[:services].map! do |service|
|
||||||
|
if service.is_a?(Hash)
|
||||||
|
service[:name]
|
||||||
|
else
|
||||||
|
service
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
expose :timeout do |model|
|
expose :timeout do |model|
|
||||||
|
|
|
@ -2,7 +2,7 @@ module Gitlab
|
||||||
module Ci
|
module Ci
|
||||||
module Build
|
module Build
|
||||||
class Image
|
class Image
|
||||||
attr_reader :name
|
attr_reader :alias, :command, :entrypoint, :name
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def from_image(job)
|
def from_image(job)
|
||||||
|
@ -21,7 +21,14 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(image)
|
def initialize(image)
|
||||||
|
if image.is_a?(String)
|
||||||
@name = image
|
@name = image
|
||||||
|
elsif image.is_a?(Hash)
|
||||||
|
@alias = image[:alias]
|
||||||
|
@command = image[:command]
|
||||||
|
@entrypoint = image[:entrypoint]
|
||||||
|
@name = image[:name]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid?
|
def valid?
|
||||||
|
|
|
@ -8,8 +8,36 @@ module Gitlab
|
||||||
class Image < Node
|
class Image < Node
|
||||||
include Validatable
|
include Validatable
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
34
lib/gitlab/ci/config/entry/service.rb
Normal file
34
lib/gitlab/ci/config/entry/service.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
module Gitlab
|
||||||
|
module Ci
|
||||||
|
class Config
|
||||||
|
module Entry
|
||||||
|
##
|
||||||
|
# Entry that represents a configuration of Docker service.
|
||||||
|
#
|
||||||
|
class Service < Image
|
||||||
|
include Validatable
|
||||||
|
|
||||||
|
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
|
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
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -194,8 +194,8 @@ FactoryGirl.define do
|
||||||
trait :extended_options do
|
trait :extended_options do
|
||||||
options do
|
options do
|
||||||
{
|
{
|
||||||
image: 'ruby:2.1',
|
image: { name: 'ruby:2.1', entrypoint: '/bin/sh' },
|
||||||
services: ['postgres'],
|
services: ['postgres', { name: 'docker:dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
|
||||||
after_script: %w(ls date),
|
after_script: %w(ls date),
|
||||||
artifacts: {
|
artifacts: {
|
||||||
name: 'artifacts_file',
|
name: 'artifacts_file',
|
||||||
|
|
|
@ -596,13 +596,12 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Image and service handling" do
|
describe "Image and service handling" do
|
||||||
|
context "when extended docker configuration is used" do
|
||||||
it "returns image and service when defined" do
|
it "returns image and service when defined" do
|
||||||
config = YAML.dump({
|
config = YAML.dump({ image: { name: "ruby:2.1" },
|
||||||
image: "ruby:2.1",
|
services: ["mysql", { name: "docker:dind", alias: "docker" }],
|
||||||
services: ["mysql"],
|
|
||||||
before_script: ["pwd"],
|
before_script: ["pwd"],
|
||||||
rspec: { script: "rspec" }
|
rspec: { script: "rspec" } })
|
||||||
})
|
|
||||||
|
|
||||||
config_processor = GitlabCiYamlProcessor.new(config, path)
|
config_processor = GitlabCiYamlProcessor.new(config, path)
|
||||||
|
|
||||||
|
@ -615,8 +614,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",
|
||||||
|
@ -626,12 +625,11 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns image and service when overridden for job" do
|
it "returns image and service when overridden for job" do
|
||||||
config = YAML.dump({
|
config = YAML.dump({ 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: { name: "ruby:2.5" },
|
||||||
})
|
services: [{ name: "postgresql", alias: "db-pg" }, "docker:dind"], script: "rspec" } })
|
||||||
|
|
||||||
config_processor = GitlabCiYamlProcessor.new(config, path)
|
config_processor = GitlabCiYamlProcessor.new(config, path)
|
||||||
|
|
||||||
|
@ -644,8 +642,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", alias: "db-pg" }, { name: "docker:dind" }]
|
||||||
},
|
},
|
||||||
allow_failure: false,
|
allow_failure: false,
|
||||||
when: "on_success",
|
when: "on_success",
|
||||||
|
@ -655,6 +653,63 @@ module Ci
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when etended docker configuration is not used" do
|
||||||
|
it "returns image and service when defined" do
|
||||||
|
config = YAML.dump({ image: "ruby:2.1",
|
||||||
|
services: ["mysql", "docker:dind"],
|
||||||
|
before_script: ["pwd"],
|
||||||
|
rspec: { script: "rspec" } })
|
||||||
|
|
||||||
|
config_processor = GitlabCiYamlProcessor.new(config, path)
|
||||||
|
|
||||||
|
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
|
||||||
|
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
|
||||||
|
stage: "test",
|
||||||
|
stage_idx: 1,
|
||||||
|
name: "rspec",
|
||||||
|
commands: "pwd\nrspec",
|
||||||
|
coverage_regex: nil,
|
||||||
|
tag_list: [],
|
||||||
|
options: {
|
||||||
|
image: { name: "ruby:2.1" },
|
||||||
|
services: [{ name: "mysql" }, { name: "docker:dind" }]
|
||||||
|
},
|
||||||
|
allow_failure: false,
|
||||||
|
when: "on_success",
|
||||||
|
environment: nil,
|
||||||
|
yaml_variables: []
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns image and service when overridden for job" do
|
||||||
|
config = YAML.dump({ image: "ruby:2.1",
|
||||||
|
services: ["mysql"],
|
||||||
|
before_script: ["pwd"],
|
||||||
|
rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } })
|
||||||
|
|
||||||
|
config_processor = GitlabCiYamlProcessor.new(config, path)
|
||||||
|
|
||||||
|
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
|
||||||
|
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
|
||||||
|
stage: "test",
|
||||||
|
stage_idx: 1,
|
||||||
|
name: "rspec",
|
||||||
|
commands: "pwd\nrspec",
|
||||||
|
coverage_regex: nil,
|
||||||
|
tag_list: [],
|
||||||
|
options: {
|
||||||
|
image: { name: "ruby:2.5" },
|
||||||
|
services: [{ name: "postgresql" }, { name: "docker:dind" }]
|
||||||
|
},
|
||||||
|
allow_failure: false,
|
||||||
|
when: "on_success",
|
||||||
|
environment: nil,
|
||||||
|
yaml_variables: []
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'Variables' do
|
describe 'Variables' do
|
||||||
let(:config_processor) { GitlabCiYamlProcessor.new(YAML.dump(config), path) }
|
let(:config_processor) { GitlabCiYamlProcessor.new(YAML.dump(config), path) }
|
||||||
|
|
||||||
|
@ -884,8 +939,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 +1316,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 +1337,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 +1379,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
|
||||||
|
|
|
@ -10,6 +10,7 @@ describe Gitlab::Ci::Build::Image do
|
||||||
let(:image_name) { 'ruby:2.1' }
|
let(:image_name) { 'ruby:2.1' }
|
||||||
let(:job) { create(:ci_build, options: { image: image_name } ) }
|
let(:job) { create(:ci_build, options: { image: image_name } ) }
|
||||||
|
|
||||||
|
context 'when image is defined as string' do
|
||||||
it 'fabricates an object of the proper class' do
|
it 'fabricates an object of the proper class' do
|
||||||
is_expected.to be_kind_of(described_class)
|
is_expected.to be_kind_of(described_class)
|
||||||
end
|
end
|
||||||
|
@ -17,6 +18,21 @@ describe Gitlab::Ci::Build::Image do
|
||||||
it 'populates fabricated object with the proper name attribute' do
|
it 'populates fabricated object with the proper name attribute' do
|
||||||
expect(subject.name).to eq(image_name)
|
expect(subject.name).to eq(image_name)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when image is defined as hash' do
|
||||||
|
let(:entrypoint) { '/bin/sh' }
|
||||||
|
let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint } } ) }
|
||||||
|
|
||||||
|
it 'fabricates an object of the proper class' do
|
||||||
|
is_expected.to be_kind_of(described_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'populates fabricated object with the proper attributes' do
|
||||||
|
expect(subject.name).to eq(image_name)
|
||||||
|
expect(subject.entrypoint).to eq(entrypoint)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when image name is empty' do
|
context 'when image name is empty' do
|
||||||
let(:image_name) { '' }
|
let(:image_name) { '' }
|
||||||
|
@ -41,11 +57,40 @@ describe Gitlab::Ci::Build::Image do
|
||||||
let(:service_image_name) { 'postgres' }
|
let(:service_image_name) { 'postgres' }
|
||||||
let(:job) { create(:ci_build, options: { services: [service_image_name] }) }
|
let(:job) { create(:ci_build, options: { services: [service_image_name] }) }
|
||||||
|
|
||||||
|
context 'when service is defined as string' do
|
||||||
it 'fabricates an non-empty array of objects' do
|
it 'fabricates an non-empty array of objects' do
|
||||||
is_expected.to be_kind_of(Array)
|
is_expected.to be_kind_of(Array)
|
||||||
is_expected.not_to be_empty
|
is_expected.not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'populates fabricated objects with the proper name attributes' do
|
||||||
|
expect(subject.first).to be_kind_of(described_class)
|
||||||
expect(subject.first.name).to eq(service_image_name)
|
expect(subject.first.name).to eq(service_image_name)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service is defined as hash' do
|
||||||
|
let(:service_entrypoint) { '/bin/sh' }
|
||||||
|
let(:service_alias) { 'db' }
|
||||||
|
let(:service_command) { 'sleep 30' }
|
||||||
|
let(:job) do
|
||||||
|
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
|
||||||
|
alias: service_alias, command: service_command }] })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fabricates an non-empty array of objects' do
|
||||||
|
is_expected.to be_kind_of(Array)
|
||||||
|
is_expected.not_to be_empty
|
||||||
|
expect(subject.first).to be_kind_of(described_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'populates fabricated objects with the proper attributes' do
|
||||||
|
expect(subject.first.name).to eq(service_image_name)
|
||||||
|
expect(subject.first.entrypoint).to eq(service_entrypoint)
|
||||||
|
expect(subject.first.alias).to eq(service_alias)
|
||||||
|
expect(subject.first.command).to eq(service_command)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when service image name is empty' do
|
context 'when service image name is empty' do
|
||||||
let(:service_image_name) { '' }
|
let(:service_image_name) { '' }
|
||||||
|
|
|
@ -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
|
||||||
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
|
||||||
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'
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
117
spec/lib/gitlab/ci/config/entry/service_spec.rb
Normal file
117
spec/lib/gitlab/ci/config/entry/service_spec.rb
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
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
|
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
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
context 'when entry value is not correct' do
|
context 'when configuration is invalid' do
|
||||||
let(:config) { 'ls' }
|
let(:config) { 'postgresql:9.5' }
|
||||||
|
|
||||||
describe '#errors' do
|
|
||||||
it 'saves errors' do
|
|
||||||
expect(entry.errors)
|
|
||||||
.to include 'services config should be an array of strings'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -356,8 +356,11 @@ describe API::Runner do
|
||||||
expect(json_response['token']).to eq(job.token)
|
expect(json_response['token']).to eq(job.token)
|
||||||
expect(json_response['job_info']).to eq(expected_job_info)
|
expect(json_response['job_info']).to eq(expected_job_info)
|
||||||
expect(json_response['git_info']).to eq(expected_git_info)
|
expect(json_response['git_info']).to eq(expected_git_info)
|
||||||
expect(json_response['image']).to eq({ 'name' => 'ruby:2.1' })
|
expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh' })
|
||||||
expect(json_response['services']).to eq([{ 'name' => 'postgres' }])
|
expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
|
||||||
|
'alias' => nil, 'command' => nil },
|
||||||
|
{ 'name' => 'docker:dind', 'entrypoint' => '/bin/sh',
|
||||||
|
'alias' => 'docker', 'command' => 'sleep 30' }])
|
||||||
expect(json_response['steps']).to eq(expected_steps)
|
expect(json_response['steps']).to eq(expected_steps)
|
||||||
expect(json_response['artifacts']).to eq(expected_artifacts)
|
expect(json_response['artifacts']).to eq(expected_artifacts)
|
||||||
expect(json_response['cache']).to eq(expected_cache)
|
expect(json_response['cache']).to eq(expected_cache)
|
||||||
|
|
|
@ -137,6 +137,18 @@ describe Ci::API::Builds do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when docker configuration options are used' do
|
||||||
|
let!(:build) { create(:ci_build, :extended_options, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
|
||||||
|
|
||||||
|
it 'starts a build' do
|
||||||
|
register_builds info: { platform: :darwin }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(201)
|
||||||
|
expect(json_response['options']['image']).to eq('ruby:2.1')
|
||||||
|
expect(json_response['options']['services']).to eq(['postgres', 'docker:dind'])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when builds are finished' do
|
context 'when builds are finished' do
|
||||||
|
|
Loading…
Reference in a new issue