GitLab wurde erfolgreich aktualisiert. Dank regelmäßiger Updates bleibt das THM GitLab sicher und Sie profitieren von den neuesten Funktionen. Danke für Ihre Geduld.

Commit bd45beaf authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'generalize-ci-config' into 'master'

ci/config: generalize Config validation into Gitlab::Config:: module

See merge request gitlab-org/gitlab-ce!23443
parents 4e85011f 64b1044e
......@@ -15,7 +15,7 @@ def initialize(config, opts = {})
@global = Entry::Global.new(@config)
@global.compose!
rescue Loader::FormatError,
rescue Gitlab::Config::Loader::FormatError,
Extendable::ExtensionError,
External::Processor::IncludeError => e
raise Config::ConfigError, e.message
......@@ -71,7 +71,7 @@ def jobs
private
def build_config(config, opts = {})
initial_config = Loader.new(config).load!
initial_config = Gitlab::Config::Loader::Yaml.new(config).load!
project = opts.fetch(:project, nil)
if project
......
......@@ -7,10 +7,10 @@ module Entry
##
# Entry that represents a configuration of job artifacts.
#
class Artifacts < Node
include Configurable
include Validatable
include Attributable
class Artifacts < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[name untracked paths reports when expire_in].freeze
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
module Attributable
extend ActiveSupport::Concern
class_methods do
def attributes(*attributes)
attributes.flatten.each do |attribute|
if method_defined?(attribute)
raise ArgumentError, 'Method already defined!'
end
define_method(attribute) do
return unless config.is_a?(Hash)
config[attribute]
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
##
# Entry that represents a boolean value.
#
class Boolean < Node
include Validatable
validations do
validates :config, boolean: true
end
end
end
end
end
end
......@@ -7,9 +7,9 @@ module Entry
##
# Entry that represents a cache configuration
#
class Cache < Node
include Configurable
include Attributable
class Cache < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[key untracked paths policy].freeze
DEFAULT_POLICY = 'pull-push'.freeze
......@@ -22,7 +22,7 @@ class Cache < Node
entry :key, Entry::Key,
description: 'Cache key used to define a cache affinity.'
entry :untracked, Entry::Boolean,
entry :untracked, ::Gitlab::Config::Entry::Boolean,
description: 'Cache all untracked files.'
entry :paths, Entry::Paths,
......
......@@ -7,8 +7,8 @@ module Entry
##
# Entry that represents a job script.
#
class Commands < Node
include Validatable
class Commands < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
validations do
validates :config, array_of_strings_or_string: true
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
##
# This mixin is responsible for adding DSL, which purpose is to
# simplifly process of adding child nodes.
#
# This can be used only if parent node is a configuration entry that
# holds a hash as a configuration value, for example:
#
# job:
# script: ...
# artifacts: ...
#
module Configurable
extend ActiveSupport::Concern
included do
include Validatable
validations do
validates :config, type: Hash
end
end
# rubocop: disable CodeReuse/ActiveRecord
def compose!(deps = nil)
return unless valid?
self.class.nodes.each do |key, factory|
factory
.value(config[key])
.with(key: key, parent: self)
entries[key] = factory.create!
end
yield if block_given?
entries.each_value do |entry|
entry.compose!(deps)
end
end
# rubocop: enable CodeReuse/ActiveRecord
class_methods do
def nodes
Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }]
end
private
# rubocop: disable CodeReuse/ActiveRecord
def entry(key, entry, metadata)
factory = Entry::Factory.new(entry)
.with(description: metadata[:description])
(@nodes ||= {}).merge!(key.to_sym => factory)
end
# rubocop: enable CodeReuse/ActiveRecord
def helpers(*nodes)
nodes.each do |symbol|
define_method("#{symbol}_defined?") do
entries[symbol]&.specified?
end
define_method("#{symbol}_value") do
return unless entries[symbol] && entries[symbol].valid?
entries[symbol].value
end
end
end
end
end
end
end
end
end
......@@ -7,8 +7,8 @@ module Entry
##
# Entry that represents Coverage settings.
#
class Coverage < Node
include Validatable
class Coverage < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
validations do
validates :config, regexp: true
......
......@@ -7,8 +7,8 @@ module Entry
##
# Entry that represents an environment.
#
class Environment < Node
include Validatable
class Environment < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
ALLOWED_KEYS = %i[name url action on_stop].freeze
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
##
# Factory class responsible for fabricating entry objects.
#
class Factory
InvalidFactory = Class.new(StandardError)
def initialize(entry)
@entry = entry
@metadata = {}
@attributes = {}
end
def value(value)
@value = value
self
end
def metadata(metadata)
@metadata.merge!(metadata)
self
end
def with(attributes)
@attributes.merge!(attributes)
self
end
def create!
raise InvalidFactory unless defined?(@value)
##
# We assume that unspecified entry is undefined.
# See issue #18775.
#
if @value.nil?
Entry::Unspecified.new(
fabricate_unspecified
)
else
fabricate(@entry, @value)
end
end
private
def fabricate_unspecified
##
# If entry has a default value we fabricate concrete node
# with default value.
#
if @entry.default.nil?
fabricate(Entry::Undefined)
else
fabricate(@entry, @entry.default)
end
end
def fabricate(entry, value = nil)
entry.new(value, @metadata).tap do |node|
node.key = @attributes[:key]
node.parent = @attributes[:parent]
node.description = @attributes[:description]
end
end
end
end
end
end
end
......@@ -8,8 +8,8 @@ module Entry
# This class represents a global entry - root Entry for entire
# GitLab CI Configuration file.
#
class Global < Node
include Configurable
class Global < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
entry :before_script, Entry::Script,
description: 'Script that will be executed before each job.'
......@@ -49,7 +49,7 @@ def compose!(_deps = nil)
# rubocop: disable CodeReuse/ActiveRecord
def compose_jobs!
factory = Entry::Factory.new(Entry::Jobs)
factory = ::Gitlab::Config::Entry::Factory.new(Entry::Jobs)
.value(@config.except(*self.class.nodes.keys))
.with(key: :jobs, parent: self,
description: 'Jobs definition for this pipeline')
......
......@@ -7,8 +7,8 @@ module Entry
##
# Entry that represents a hidden CI/CD key.
#
class Hidden < Node
include Validatable
class Hidden < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
validations do
validates :config, presence: true
......
......@@ -7,8 +7,8 @@ module Entry
##
# Entry that represents a Docker image.
#
class Image < Node
include Validatable
class Image < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
ALLOWED_KEYS = %i[name entrypoint].freeze
......
......@@ -7,9 +7,9 @@ module Entry
##
# Entry that represents a concrete CI/CD job.
#
class Job < Node
include Configurable
include Attributable
class Job < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[tags script only except type image services
allow_failure type stage when start_in artifacts cache
......
......@@ -7,8 +7,8 @@ module Entry
##
# Entry that represents a set of jobs.
#
class Jobs < Node
include Validatable
class Jobs < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
validations do
validates :config, type: Hash
......@@ -34,7 +34,7 @@ def compose!(deps = nil)
@config.each do |name, config|
node = hidden?(name) ? Entry::Hidden : Entry::Job
factory = Entry::Factory.new(node)
factory = ::Gitlab::Config::Entry::Factory.new(node)
.value(config || {})
.metadata(name: name)
.with(key: name, parent: self,
......
......@@ -7,8 +7,8 @@ module Entry
##
# Entry that represents a key.
#
class Key < Node
include Validatable
class Key < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
validations do
validates :config, key: true
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
module LegacyValidationHelpers
private
def validate_duration(value)
value.is_a?(String) && ChronicDuration.parse(value)
rescue ChronicDuration::DurationParseError
false
end
def validate_duration_limit(value, limit)
return false unless value.is_a?(String)
ChronicDuration.parse(value).second.from_now <
ChronicDuration.parse(limit).second.from_now
rescue ChronicDuration::DurationParseError
false
end
def validate_array_of_strings(values)
values.is_a?(Array) && values.all? { |value| validate_string(value) }
end
def validate_array_of_strings_or_regexps(values)
values.is_a?(Array) && values.all? { |value| validate_string_or_regexp(value) }
end
def validate_variables(variables)
variables.is_a?(Hash) &&
variables.flatten.all? do |value|
validate_string(value) || validate_integer(value)
end
end
def validate_integer(value)
value.is_a?(Integer)
end
def validate_string(value)
value.is_a?(String) || value.is_a?(Symbol)
end
def validate_regexp(value)
!value.nil? && Regexp.new(value.to_s) && true
rescue RegexpError, TypeError
false
end
def validate_string_or_regexp(value)
return true if value.is_a?(Symbol)
return false unless value.is_a?(String)
if value.first == '/' && value.last == '/'
validate_regexp(value[1...-1])
else
true
end
end
def validate_boolean(value)
value.in?([true, false])
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
##
# Base abstract class for each configuration entry node.
#
class Node
InvalidError = Class.new(StandardError)
attr_reader :config, :metadata
attr_accessor :key, :parent, :description
def initialize(config, **metadata)
@config = config
@metadata = metadata
@entries = {}
self.class.aspects.to_a.each do |aspect|
instance_exec(&aspect)
end
end
def [](key)
@entries[key] || Entry::Undefined.new
end
def compose!(deps = nil)
return unless valid?
yield if block_given?
end
def leaf?
@entries.none?
end
def descendants
@entries.values
end
def ancestors
@parent ? @parent.ancestors + [@parent] : []
end
def valid?
errors.none?
end
def errors
[]
end
def value
if leaf?
@config
else
meaningful = @entries.select do |_key, value|
value.specified? && value.relevant?
end
Hash[meaningful.map { |key, entry| [key, entry.value] }]
end
end
def specified?
true
end
def relevant?
true
end
def location
name = @key.presence || self.class.name.to_s.demodulize
.underscore.humanize.downcase
ancestors.map(&:key).append(name).compact.join(':')
end
def inspect
val = leaf? ? config : descendants
unspecified = specified? ? '' : '(unspecified) '
"#<#{self.class.name} #{unspecified}{#{key}: #{val.inspect}}>"
end
def self.default
end
def self.aspects
@aspects ||= []
end
private
attr_reader :entries
end
end
end
end
end
......@@ -7,8 +7,8 @@ module Entry
##
# Entry that represents an array of paths.
#
class Paths < Node
include Validatable
class Paths < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
validations do
validates :config, array_of_strings: true
......
......@@ -7,12 +7,12 @@ module Entry
##
# Entry that represents an only/except trigger policy for the job.
#
class Policy < Simplifiable
class Policy < ::Gitlab::Config::Entry::Simplifiable
strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) }