Commit 0db50a80 authored by Markus Doits's avatar Markus Doits

update job config validator to validate new retry syntax

parent 42f36954
...@@ -16,6 +16,8 @@ module Gitlab ...@@ -16,6 +16,8 @@ module Gitlab
dependencies before_script after_script variables dependencies before_script after_script variables
environment coverage retry parallel extends].freeze environment coverage retry parallel extends].freeze
ALLOWED_KEYS_RETRY = %i[max when].freeze
validations do validations do
validates :config, allowed_keys: ALLOWED_KEYS validates :config, allowed_keys: ALLOWED_KEYS
validates :config, presence: true validates :config, presence: true
...@@ -23,12 +25,57 @@ module Gitlab ...@@ -23,12 +25,57 @@ module Gitlab
validates :name, presence: true validates :name, presence: true
validates :name, type: Symbol validates :name, type: Symbol
validates :retry, hash_or_integer: true, allowed_keys: ALLOWED_KEYS_RETRY, allow_nil: true
validate :validate_retry
def validate_retry
return if !config ||
!config.is_a?(Hash) ||
config[:retry].nil? ||
!config[:retry].is_a?(Integer) && !config[:retry].is_a?(Hash)
check =
if config[:retry].is_a?(Integer)
{ max: config[:retry] }
else
config[:retry]
end
validate_retry_max(check[:max])
validate_retry_when(check[:when])
end
def validate_retry_max(retry_max)
if retry_max.is_a?(Integer)
errors[:base] << "retry max #{::I18n.t('errors.messages.less_than_or_equal_to', count: 2)}" if retry_max > 2
errors[:base] << "retry max #{::I18n.t('errors.messages.greater_than_or_equal_to', count: 0)}" if retry_max < 0
else
errors[:base] << "retry max #{::I18n.t('errors.messages.not_an_integer')}"
end
end
def validate_retry_when(retry_when)
return if retry_when.blank?
possible_failures = Gitlab::Ci::Status::Build::Failed.reasons.keys.map(&:to_s) + ['always']
if retry_when.is_a?(String)
unless possible_failures.include?(retry_when)
errors[:base] << 'retry when is unknown'
end
elsif retry_when.is_a?(Array)
unknown_whens = retry_when - possible_failures
unless unknown_whens.empty?
errors[:base] << "retry when cannot have unknown failures #{unknown_whens.join(', ')}"
end
else
errors[:base] << 'retry when should be an array of strings or a string'
end
end
with_options allow_nil: true do with_options allow_nil: true do
validates :tags, array_of_strings: true validates :tags, array_of_strings: true
validates :allow_failure, boolean: true validates :allow_failure, boolean: true
validates :retry, numericality: { only_integer: true,
greater_than_or_equal_to: 0,
less_than_or_equal_to: 2 }
validates :parallel, numericality: { only_integer: true, validates :parallel, numericality: { only_integer: true,
greater_than_or_equal_to: 2 } greater_than_or_equal_to: 2 }
validates :when, validates :when,
...@@ -160,7 +207,7 @@ module Gitlab ...@@ -160,7 +207,7 @@ module Gitlab
environment: environment_defined? ? environment_value : nil, environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil, environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil, coverage: coverage_defined? ? coverage_value : nil,
retry: retry_defined? ? retry_value.to_i : nil, retry: retry_defined? ? retry_value : nil,
parallel: parallel_defined? ? parallel_value.to_i : nil, parallel: parallel_defined? ? parallel_value.to_i : nil,
artifacts: artifacts_value, artifacts: artifacts_value,
after_script: after_script_value, after_script: after_script_value,
......
...@@ -7,10 +7,10 @@ module Gitlab ...@@ -7,10 +7,10 @@ module Gitlab
module Validators module Validators
class AllowedKeysValidator < ActiveModel::EachValidator class AllowedKeysValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
unknown_keys = record.config.try(:keys).to_a - options[:in] unknown_keys = value.try(:keys).to_a - options[:in]
if unknown_keys.any? if unknown_keys.any?
record.errors.add(:config, 'contains unknown keys: ' + record.errors.add(:config, "#{attribute} contains unknown keys: " +
unknown_keys.join(', ')) unknown_keys.join(', '))
end end
end end
...@@ -68,6 +68,14 @@ module Gitlab ...@@ -68,6 +68,14 @@ module Gitlab
end end
end end
class HashOrIntegerValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.is_a?(Hash) || value.is_a?(Integer)
record.errors.add(attribute, 'should be a hash or an integer')
end
end
end
class KeyValidator < ActiveModel::EachValidator class KeyValidator < ActiveModel::EachValidator
include LegacyValidationHelpers include LegacyValidationHelpers
......
...@@ -98,41 +98,152 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -98,41 +98,152 @@ describe Gitlab::Ci::Config::Entry::Job do
end end
end end
context 'when retry value is correct' do
context 'when it is a numeric' do
let(:config) { { script: 'rspec', retry: 2 } }
it 'is valid' do
expect(entry).to be_valid
end
end
context 'when it is a hash without when' do
let(:config) { { script: 'rspec', retry: { max: 2 } } }
it 'is valid' do
expect(entry).to be_valid
end
end
context 'when it is a hash with string when' do
let(:config) { { script: 'rspec', retry: { max: 2, when: 'unknown_failure' } } }
it 'is valid' do
expect(entry).to be_valid
end
end
context 'when it is a hash with string when always' do
let(:config) { { script: 'rspec', retry: { max: 2, when: 'always' } } }
it 'is valid' do
expect(entry).to be_valid
end
end
context 'when it is a hash with array when' do
let(:config) { { script: 'rspec', retry: { max: 2, when: %w[unknown_failure runner_system_failure] } } }
it 'is valid' do
expect(entry).to be_valid
end
end
end
context 'when retry value is not correct' do context 'when retry value is not correct' do
context 'when it is not a numeric value' do context 'when it is not a numeric nor an array' do
let(:config) { { retry: true } } let(:config) { { retry: true } }
it 'returns error about invalid type' do it 'returns error about invalid type' do
expect(entry).not_to be_valid expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry is not a number' expect(entry.errors).to include 'job retry should be a hash or an integer'
end end
end end
context 'when it is lower than zero' do context 'not defined as a hash' do
let(:config) { { retry: -1 } } context 'when it is lower than zero' do
let(:config) { { retry: -1 } }
it 'returns error about value too low' do it 'returns error about value too low' do
expect(entry).not_to be_valid expect(entry).not_to be_valid
expect(entry.errors) expect(entry.errors)
.to include 'job retry must be greater than or equal to 0' .to include 'job retry max must be greater than or equal to 0'
end
end end
end
context 'when it is not an integer' do context 'when it is not an integer' do
let(:config) { { retry: 1.5 } } let(:config) { { retry: 1.5 } }
it 'returns error about wrong value' do it 'returns error about wrong value' do
expect(entry).not_to be_valid expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry must be an integer' expect(entry.errors).to include 'job retry should be a hash or an integer'
end
end
context 'when the value is too high' do
let(:config) { { retry: 10 } }
it 'returns error about value too high' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry max must be less than or equal to 2'
end
end end
end end
context 'when the value is too high' do context 'defined as a hash' do
let(:config) { { retry: 10 } } context 'with unkown keys' do
let(:config) { { retry: { max: 2, unknown_key: :something, one_more: :key } } }
it 'returns error about value too high' do it 'returns error about the unknown key' do
expect(entry).not_to be_valid expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry must be less than or equal to 2' expect(entry.errors)
.to include 'job config retry contains unknown keys: unknown_key, one_more'
end
end
context 'when max is lower than zero' do
let(:config) { { retry: { max: -1 } } }
it 'returns error about value too low' do
expect(entry).not_to be_valid
expect(entry.errors)
.to include 'job retry max must be greater than or equal to 0'
end
end
context 'when max is not an integer' do
let(:config) { { retry: { max: 1.5 } } }
it 'returns error about wrong value' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry max must be an integer'
end
end
context 'when max is too high' do
let(:config) { { retry: { max: 10 } } }
it 'returns error about value too high' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry max must be less than or equal to 2'
end
end
context 'when when has the wrong format' do
let(:config) { { retry: { when: true } } }
it 'returns error about the wrong format' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry max must be an integer'
end
end
context 'when when is a string and unknown' do
let(:config) { { retry: { when: 'unknown_reason' } } }
it 'returns error about the wrong format' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry when is unknown'
end
end
context 'when when is an array and includes unknown failures' do
let(:config) { { retry: { when: %w[unknown_reason runner_system_failure] } } }
it 'returns error about the wrong format' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry when cannot have unknown failures unknown_reason'
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