gitlab-ci-yaml_lint 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.md +50 -0
- data/Rakefile +6 -0
- data/bin/gitlab-ci-yaml_lint +13 -0
- data/gitlab-ci-yaml_lint.gemspec +31 -0
- data/gitlab_lib/LICENSE +27 -0
- data/gitlab_lib/gitlab/ci/config.rb +62 -0
- data/gitlab_lib/gitlab/ci/config/entry/artifacts.rb +35 -0
- data/gitlab_lib/gitlab/ci/config/entry/attributable.rb +27 -0
- data/gitlab_lib/gitlab/ci/config/entry/boolean.rb +18 -0
- data/gitlab_lib/gitlab/ci/config/entry/cache.rb +45 -0
- data/gitlab_lib/gitlab/ci/config/entry/commands.rb +33 -0
- data/gitlab_lib/gitlab/ci/config/entry/configurable.rb +77 -0
- data/gitlab_lib/gitlab/ci/config/entry/coverage.rb +22 -0
- data/gitlab_lib/gitlab/ci/config/entry/environment.rb +83 -0
- data/gitlab_lib/gitlab/ci/config/entry/factory.rb +73 -0
- data/gitlab_lib/gitlab/ci/config/entry/global.rb +72 -0
- data/gitlab_lib/gitlab/ci/config/entry/hidden.rb +22 -0
- data/gitlab_lib/gitlab/ci/config/entry/image.rb +47 -0
- data/gitlab_lib/gitlab/ci/config/entry/job.rb +157 -0
- data/gitlab_lib/gitlab/ci/config/entry/jobs.rb +52 -0
- data/gitlab_lib/gitlab/ci/config/entry/key.rb +22 -0
- data/gitlab_lib/gitlab/ci/config/entry/legacy_validation_helpers.rb +61 -0
- data/gitlab_lib/gitlab/ci/config/entry/node.rb +101 -0
- data/gitlab_lib/gitlab/ci/config/entry/paths.rb +18 -0
- data/gitlab_lib/gitlab/ci/config/entry/policy.rb +53 -0
- data/gitlab_lib/gitlab/ci/config/entry/script.rb +18 -0
- data/gitlab_lib/gitlab/ci/config/entry/service.rb +34 -0
- data/gitlab_lib/gitlab/ci/config/entry/services.rb +41 -0
- data/gitlab_lib/gitlab/ci/config/entry/simplifiable.rb +43 -0
- data/gitlab_lib/gitlab/ci/config/entry/stage.rb +22 -0
- data/gitlab_lib/gitlab/ci/config/entry/stages.rb +22 -0
- data/gitlab_lib/gitlab/ci/config/entry/undefined.rb +40 -0
- data/gitlab_lib/gitlab/ci/config/entry/unspecified.rb +19 -0
- data/gitlab_lib/gitlab/ci/config/entry/validatable.rb +38 -0
- data/gitlab_lib/gitlab/ci/config/entry/validator.rb +26 -0
- data/gitlab_lib/gitlab/ci/config/entry/validators.rb +144 -0
- data/gitlab_lib/gitlab/ci/config/entry/variables.rb +26 -0
- data/gitlab_lib/gitlab/ci/config/loader.rb +25 -0
- data/gitlab_lib/gitlab/ci/yaml_processor.rb +189 -0
- data/lib/gitlab/ci/yaml_lint.rb +73 -0
- data/lib/gitlab/ci/yaml_lint/rake/tasks.rb +28 -0
- data/lib/gitlab/ci/yaml_lint/version.rb +7 -0
- metadata +203 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
##
|
6
|
+
# Entry that represents a set of jobs.
|
7
|
+
#
|
8
|
+
class Jobs < Node
|
9
|
+
include Validatable
|
10
|
+
|
11
|
+
validations do
|
12
|
+
validates :config, type: Hash
|
13
|
+
|
14
|
+
validate do
|
15
|
+
unless has_visible_job?
|
16
|
+
errors.add(:config, 'should contain at least one visible job')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_visible_job?
|
21
|
+
config.any? { |name, _| !hidden?(name) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def hidden?(name)
|
26
|
+
name.to_s.start_with?('.')
|
27
|
+
end
|
28
|
+
|
29
|
+
def compose!(deps = nil)
|
30
|
+
super do
|
31
|
+
@config.each do |name, config|
|
32
|
+
node = hidden?(name) ? Entry::Hidden : Entry::Job
|
33
|
+
|
34
|
+
factory = Entry::Factory.new(node)
|
35
|
+
.value(config || {})
|
36
|
+
.metadata(name: name)
|
37
|
+
.with(key: name, parent: self,
|
38
|
+
description: "#{name} job definition.")
|
39
|
+
|
40
|
+
@entries[name] = factory.create!
|
41
|
+
end
|
42
|
+
|
43
|
+
@entries.each_value do |entry|
|
44
|
+
entry.compose!(deps)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
##
|
6
|
+
# Entry that represents a key.
|
7
|
+
#
|
8
|
+
class Key < Node
|
9
|
+
include Validatable
|
10
|
+
|
11
|
+
validations do
|
12
|
+
validates :config, key: true
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.default
|
16
|
+
'default'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
module LegacyValidationHelpers
|
6
|
+
private
|
7
|
+
|
8
|
+
def validate_duration(value)
|
9
|
+
value.is_a?(String) && ChronicDuration.parse(value)
|
10
|
+
rescue ChronicDuration::DurationParseError
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_array_of_strings(values)
|
15
|
+
values.is_a?(Array) && values.all? { |value| validate_string(value) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_array_of_strings_or_regexps(values)
|
19
|
+
values.is_a?(Array) && values.all? { |value| validate_string_or_regexp(value) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_variables(variables)
|
23
|
+
variables.is_a?(Hash) &&
|
24
|
+
variables.flatten.all? do |value|
|
25
|
+
validate_string(value) || validate_integer(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_integer(value)
|
30
|
+
value.is_a?(Integer)
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_string(value)
|
34
|
+
value.is_a?(String) || value.is_a?(Symbol)
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_regexp(value)
|
38
|
+
!value.nil? && Regexp.new(value.to_s) && true
|
39
|
+
rescue RegexpError, TypeError
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_string_or_regexp(value)
|
44
|
+
return true if value.is_a?(Symbol)
|
45
|
+
return false unless value.is_a?(String)
|
46
|
+
|
47
|
+
if value.first == '/' && value.last == '/'
|
48
|
+
validate_regexp(value[1...-1])
|
49
|
+
else
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate_boolean(value)
|
55
|
+
value.in?([true, false])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
##
|
6
|
+
# Base abstract class for each configuration entry node.
|
7
|
+
#
|
8
|
+
class Node
|
9
|
+
InvalidError = Class.new(StandardError)
|
10
|
+
|
11
|
+
attr_reader :config, :metadata
|
12
|
+
attr_accessor :key, :parent, :description
|
13
|
+
|
14
|
+
def initialize(config, **metadata)
|
15
|
+
@config = config
|
16
|
+
@metadata = metadata
|
17
|
+
@entries = {}
|
18
|
+
|
19
|
+
self.class.aspects.to_a.each do |aspect|
|
20
|
+
instance_exec(&aspect)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](key)
|
25
|
+
@entries[key] || Entry::Undefined.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def compose!(deps = nil)
|
29
|
+
return unless valid?
|
30
|
+
|
31
|
+
yield if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def leaf?
|
35
|
+
@entries.none?
|
36
|
+
end
|
37
|
+
|
38
|
+
def descendants
|
39
|
+
@entries.values
|
40
|
+
end
|
41
|
+
|
42
|
+
def ancestors
|
43
|
+
@parent ? @parent.ancestors + [@parent] : []
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid?
|
47
|
+
errors.none?
|
48
|
+
end
|
49
|
+
|
50
|
+
def errors
|
51
|
+
[]
|
52
|
+
end
|
53
|
+
|
54
|
+
def value
|
55
|
+
if leaf?
|
56
|
+
@config
|
57
|
+
else
|
58
|
+
meaningful = @entries.select do |_key, value|
|
59
|
+
value.specified? && value.relevant?
|
60
|
+
end
|
61
|
+
|
62
|
+
Hash[meaningful.map { |key, entry| [key, entry.value] }]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def specified?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
def relevant?
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def location
|
75
|
+
name = @key.presence || self.class.name.to_s.demodulize
|
76
|
+
.underscore.humanize.downcase
|
77
|
+
|
78
|
+
ancestors.map(&:key).append(name).compact.join(':')
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
val = leaf? ? config : descendants
|
83
|
+
unspecified = specified? ? '' : '(unspecified) '
|
84
|
+
"#<#{self.class.name} #{unspecified}{#{key}: #{val.inspect}}>"
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.default
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.aspects
|
91
|
+
@aspects ||= []
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
attr_reader :entries
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
##
|
6
|
+
# Entry that represents an array of paths.
|
7
|
+
#
|
8
|
+
class Paths < Node
|
9
|
+
include Validatable
|
10
|
+
|
11
|
+
validations do
|
12
|
+
validates :config, array_of_strings: true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
##
|
6
|
+
# Entry that represents an only/except trigger policy for the job.
|
7
|
+
#
|
8
|
+
class Policy < Simplifiable
|
9
|
+
strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) }
|
10
|
+
strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) }
|
11
|
+
|
12
|
+
class RefsPolicy < Entry::Node
|
13
|
+
include Entry::Validatable
|
14
|
+
|
15
|
+
validations do
|
16
|
+
validates :config, array_of_strings_or_regexps: true
|
17
|
+
end
|
18
|
+
|
19
|
+
def value
|
20
|
+
{ refs: @config }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ComplexPolicy < Entry::Node
|
25
|
+
include Entry::Validatable
|
26
|
+
include Entry::Attributable
|
27
|
+
|
28
|
+
attributes :refs, :kubernetes
|
29
|
+
|
30
|
+
validations do
|
31
|
+
validates :config, presence: true
|
32
|
+
validates :config, allowed_keys: %i[refs kubernetes]
|
33
|
+
|
34
|
+
with_options allow_nil: true do
|
35
|
+
validates :refs, array_of_strings_or_regexps: true
|
36
|
+
validates :kubernetes, allowed_values: %w[active]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class UnknownStrategy < Entry::Node
|
42
|
+
def errors
|
43
|
+
["#{location} has to be either an array of conditions or a hash"]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.default
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
##
|
6
|
+
# Entry that represents a script.
|
7
|
+
#
|
8
|
+
class Script < Node
|
9
|
+
include Validatable
|
10
|
+
|
11
|
+
validations do
|
12
|
+
validates :config, array_of_strings: true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
##
|
6
|
+
# Entry that represents a configuration of Docker service.
|
7
|
+
#
|
8
|
+
class Service < Image
|
9
|
+
include Validatable
|
10
|
+
|
11
|
+
ALLOWED_KEYS = %i[name entrypoint command alias].freeze
|
12
|
+
|
13
|
+
validations do
|
14
|
+
validates :config, hash_or_string: true
|
15
|
+
validates :config, allowed_keys: ALLOWED_KEYS
|
16
|
+
|
17
|
+
validates :name, type: String, presence: true
|
18
|
+
validates :entrypoint, array_of_strings: true, allow_nil: true
|
19
|
+
validates :command, array_of_strings: true, allow_nil: true
|
20
|
+
validates :alias, type: String, allow_nil: true
|
21
|
+
end
|
22
|
+
|
23
|
+
def alias
|
24
|
+
value[:alias]
|
25
|
+
end
|
26
|
+
|
27
|
+
def command
|
28
|
+
value[:command]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
##
|
6
|
+
# Entry that represents a configuration of Docker services.
|
7
|
+
#
|
8
|
+
class Services < Node
|
9
|
+
include Validatable
|
10
|
+
|
11
|
+
validations do
|
12
|
+
validates :config, type: Array
|
13
|
+
end
|
14
|
+
|
15
|
+
def compose!(deps = nil)
|
16
|
+
super do
|
17
|
+
@entries = []
|
18
|
+
@config.each do |config|
|
19
|
+
@entries << Entry::Factory.new(Entry::Service)
|
20
|
+
.value(config || {})
|
21
|
+
.create!
|
22
|
+
end
|
23
|
+
|
24
|
+
@entries.each do |entry|
|
25
|
+
entry.compose!(deps)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def value
|
31
|
+
@entries.map(&:value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def descendants
|
35
|
+
@entries
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Ci
|
3
|
+
class Config
|
4
|
+
module Entry
|
5
|
+
class Simplifiable < SimpleDelegator
|
6
|
+
EntryStrategy = Struct.new(:name, :condition)
|
7
|
+
|
8
|
+
def initialize(config, **metadata)
|
9
|
+
unless self.class.const_defined?(:UnknownStrategy)
|
10
|
+
raise ArgumentError, 'UndefinedStrategy not available!'
|
11
|
+
end
|
12
|
+
|
13
|
+
strategy = self.class.strategies.find do |variant|
|
14
|
+
variant.condition.call(config)
|
15
|
+
end
|
16
|
+
|
17
|
+
entry = self.class.entry_class(strategy)
|
18
|
+
|
19
|
+
super(entry.new(config, metadata))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.strategy(name, **opts)
|
23
|
+
EntryStrategy.new(name, opts.fetch(:if)).tap do |strategy|
|
24
|
+
strategies.append(strategy)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.strategies
|
29
|
+
@strategies ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.entry_class(strategy)
|
33
|
+
if strategy.present?
|
34
|
+
self.const_get(strategy.name)
|
35
|
+
else
|
36
|
+
self::UnknownStrategy
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|