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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE +20 -0
  7. data/README.md +50 -0
  8. data/Rakefile +6 -0
  9. data/bin/gitlab-ci-yaml_lint +13 -0
  10. data/gitlab-ci-yaml_lint.gemspec +31 -0
  11. data/gitlab_lib/LICENSE +27 -0
  12. data/gitlab_lib/gitlab/ci/config.rb +62 -0
  13. data/gitlab_lib/gitlab/ci/config/entry/artifacts.rb +35 -0
  14. data/gitlab_lib/gitlab/ci/config/entry/attributable.rb +27 -0
  15. data/gitlab_lib/gitlab/ci/config/entry/boolean.rb +18 -0
  16. data/gitlab_lib/gitlab/ci/config/entry/cache.rb +45 -0
  17. data/gitlab_lib/gitlab/ci/config/entry/commands.rb +33 -0
  18. data/gitlab_lib/gitlab/ci/config/entry/configurable.rb +77 -0
  19. data/gitlab_lib/gitlab/ci/config/entry/coverage.rb +22 -0
  20. data/gitlab_lib/gitlab/ci/config/entry/environment.rb +83 -0
  21. data/gitlab_lib/gitlab/ci/config/entry/factory.rb +73 -0
  22. data/gitlab_lib/gitlab/ci/config/entry/global.rb +72 -0
  23. data/gitlab_lib/gitlab/ci/config/entry/hidden.rb +22 -0
  24. data/gitlab_lib/gitlab/ci/config/entry/image.rb +47 -0
  25. data/gitlab_lib/gitlab/ci/config/entry/job.rb +157 -0
  26. data/gitlab_lib/gitlab/ci/config/entry/jobs.rb +52 -0
  27. data/gitlab_lib/gitlab/ci/config/entry/key.rb +22 -0
  28. data/gitlab_lib/gitlab/ci/config/entry/legacy_validation_helpers.rb +61 -0
  29. data/gitlab_lib/gitlab/ci/config/entry/node.rb +101 -0
  30. data/gitlab_lib/gitlab/ci/config/entry/paths.rb +18 -0
  31. data/gitlab_lib/gitlab/ci/config/entry/policy.rb +53 -0
  32. data/gitlab_lib/gitlab/ci/config/entry/script.rb +18 -0
  33. data/gitlab_lib/gitlab/ci/config/entry/service.rb +34 -0
  34. data/gitlab_lib/gitlab/ci/config/entry/services.rb +41 -0
  35. data/gitlab_lib/gitlab/ci/config/entry/simplifiable.rb +43 -0
  36. data/gitlab_lib/gitlab/ci/config/entry/stage.rb +22 -0
  37. data/gitlab_lib/gitlab/ci/config/entry/stages.rb +22 -0
  38. data/gitlab_lib/gitlab/ci/config/entry/undefined.rb +40 -0
  39. data/gitlab_lib/gitlab/ci/config/entry/unspecified.rb +19 -0
  40. data/gitlab_lib/gitlab/ci/config/entry/validatable.rb +38 -0
  41. data/gitlab_lib/gitlab/ci/config/entry/validator.rb +26 -0
  42. data/gitlab_lib/gitlab/ci/config/entry/validators.rb +144 -0
  43. data/gitlab_lib/gitlab/ci/config/entry/variables.rb +26 -0
  44. data/gitlab_lib/gitlab/ci/config/loader.rb +25 -0
  45. data/gitlab_lib/gitlab/ci/yaml_processor.rb +189 -0
  46. data/lib/gitlab/ci/yaml_lint.rb +73 -0
  47. data/lib/gitlab/ci/yaml_lint/rake/tasks.rb +28 -0
  48. data/lib/gitlab/ci/yaml_lint/version.rb +7 -0
  49. 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