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,22 @@
1
+ module Gitlab
2
+ module Ci
3
+ class Config
4
+ module Entry
5
+ ##
6
+ # Entry that represents Coverage settings.
7
+ #
8
+ class Coverage < Node
9
+ include Validatable
10
+
11
+ validations do
12
+ validates :config, regexp: true
13
+ end
14
+
15
+ def value
16
+ @config[1...-1]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,83 @@
1
+ module Gitlab
2
+ module Ci
3
+ class Config
4
+ module Entry
5
+ ##
6
+ # Entry that represents an environment.
7
+ #
8
+ class Environment < Node
9
+ include Validatable
10
+
11
+ ALLOWED_KEYS = %i[name url action on_stop].freeze
12
+
13
+ validations do
14
+ validate do
15
+ unless hash? || string?
16
+ errors.add(:config, 'should be a hash or a string')
17
+ end
18
+ end
19
+
20
+ validates :name, presence: true
21
+ validates :name,
22
+ type: {
23
+ with: String,
24
+ message: Gitlab::Regex.environment_name_regex_message
25
+ }
26
+
27
+ validates :name,
28
+ format: {
29
+ with: Gitlab::Regex.environment_name_regex,
30
+ message: Gitlab::Regex.environment_name_regex_message
31
+ }
32
+
33
+ with_options if: :hash? do
34
+ validates :config, allowed_keys: ALLOWED_KEYS
35
+
36
+ validates :url,
37
+ length: { maximum: 255 },
38
+ allow_nil: true
39
+
40
+ validates :action,
41
+ inclusion: { in: %w[start stop], message: 'should be start or stop' },
42
+ allow_nil: true
43
+
44
+ validates :on_stop, type: String, allow_nil: true
45
+ end
46
+ end
47
+
48
+ def hash?
49
+ @config.is_a?(Hash)
50
+ end
51
+
52
+ def string?
53
+ @config.is_a?(String)
54
+ end
55
+
56
+ def name
57
+ value[:name]
58
+ end
59
+
60
+ def url
61
+ value[:url]
62
+ end
63
+
64
+ def action
65
+ value[:action] || 'start'
66
+ end
67
+
68
+ def on_stop
69
+ value[:on_stop]
70
+ end
71
+
72
+ def value
73
+ case @config
74
+ when String then { name: @config, action: 'start' }
75
+ when Hash then @config
76
+ else {}
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,73 @@
1
+ module Gitlab
2
+ module Ci
3
+ class Config
4
+ module Entry
5
+ ##
6
+ # Factory class responsible for fabricating entry objects.
7
+ #
8
+ class Factory
9
+ InvalidFactory = Class.new(StandardError)
10
+
11
+ def initialize(entry)
12
+ @entry = entry
13
+ @metadata = {}
14
+ @attributes = {}
15
+ end
16
+
17
+ def value(value)
18
+ @value = value
19
+ self
20
+ end
21
+
22
+ def metadata(metadata)
23
+ @metadata.merge!(metadata)
24
+ self
25
+ end
26
+
27
+ def with(attributes)
28
+ @attributes.merge!(attributes)
29
+ self
30
+ end
31
+
32
+ def create!
33
+ raise InvalidFactory unless defined?(@value)
34
+
35
+ ##
36
+ # We assume that unspecified entry is undefined.
37
+ # See issue #18775.
38
+ #
39
+ if @value.nil?
40
+ Entry::Unspecified.new(
41
+ fabricate_unspecified
42
+ )
43
+ else
44
+ fabricate(@entry, @value)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def fabricate_unspecified
51
+ ##
52
+ # If entry has a default value we fabricate concrete node
53
+ # with default value.
54
+ #
55
+ if @entry.default.nil?
56
+ fabricate(Entry::Undefined)
57
+ else
58
+ fabricate(@entry, @entry.default)
59
+ end
60
+ end
61
+
62
+ def fabricate(entry, value = nil)
63
+ entry.new(value, @metadata).tap do |node|
64
+ node.key = @attributes[:key]
65
+ node.parent = @attributes[:parent]
66
+ node.description = @attributes[:description]
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,72 @@
1
+ module Gitlab
2
+ module Ci
3
+ class Config
4
+ module Entry
5
+ ##
6
+ # This class represents a global entry - root Entry for entire
7
+ # GitLab CI Configuration file.
8
+ #
9
+ class Global < Node
10
+ include Configurable
11
+
12
+ entry :before_script, Entry::Script,
13
+ description: 'Script that will be executed before each job.'
14
+
15
+ entry :image, Entry::Image,
16
+ description: 'Docker image that will be used to execute jobs.'
17
+
18
+ entry :services, Entry::Services,
19
+ description: 'Docker images that will be linked to the container.'
20
+
21
+ entry :after_script, Entry::Script,
22
+ description: 'Script that will be executed after each job.'
23
+
24
+ entry :variables, Entry::Variables,
25
+ description: 'Environment variables that will be used.'
26
+
27
+ entry :stages, Entry::Stages,
28
+ description: 'Configuration of stages for this pipeline.'
29
+
30
+ entry :types, Entry::Stages,
31
+ description: 'Deprecated: stages for this pipeline.'
32
+
33
+ entry :cache, Entry::Cache,
34
+ description: 'Configure caching between build jobs.'
35
+
36
+ helpers :before_script, :image, :services, :after_script,
37
+ :variables, :stages, :types, :cache, :jobs
38
+
39
+ def compose!(_deps = nil)
40
+ super(self) do
41
+ compose_jobs!
42
+ compose_deprecated_entries!
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def compose_jobs!
49
+ factory = Entry::Factory.new(Entry::Jobs)
50
+ .value(@config.except(*self.class.nodes.keys))
51
+ .with(key: :jobs, parent: self,
52
+ description: 'Jobs definition for this pipeline')
53
+
54
+ @entries[:jobs] = factory.create!
55
+ end
56
+
57
+ def compose_deprecated_entries!
58
+ ##
59
+ # Deprecated `:types` key workaround - if types are defined and
60
+ # stages are not defined we use types definition as stages.
61
+ #
62
+ if types_defined? && !stages_defined?
63
+ @entries[:stages] = @entries[:types]
64
+ end
65
+
66
+ @entries.delete(:types)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,22 @@
1
+ module Gitlab
2
+ module Ci
3
+ class Config
4
+ module Entry
5
+ ##
6
+ # Entry that represents a hidden CI/CD key.
7
+ #
8
+ class Hidden < Node
9
+ include Validatable
10
+
11
+ validations do
12
+ validates :config, presence: true
13
+ end
14
+
15
+ def relevant?
16
+ false
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,47 @@
1
+ module Gitlab
2
+ module Ci
3
+ class Config
4
+ module Entry
5
+ ##
6
+ # Entry that represents a Docker image.
7
+ #
8
+ class Image < Node
9
+ include Validatable
10
+
11
+ ALLOWED_KEYS = %i[name entrypoint].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
+ end
20
+
21
+ def hash?
22
+ @config.is_a?(Hash)
23
+ end
24
+
25
+ def string?
26
+ @config.is_a?(String)
27
+ end
28
+
29
+ def name
30
+ value[:name]
31
+ end
32
+
33
+ def entrypoint
34
+ value[:entrypoint]
35
+ end
36
+
37
+ def value
38
+ return { name: @config } if string?
39
+ return @config if hash?
40
+
41
+ {}
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,157 @@
1
+ module Gitlab
2
+ module Ci
3
+ class Config
4
+ module Entry
5
+ ##
6
+ # Entry that represents a concrete CI/CD job.
7
+ #
8
+ class Job < Node
9
+ include Configurable
10
+ include Attributable
11
+
12
+ ALLOWED_KEYS = %i[tags script only except type image services allow_failure
13
+ type stage when artifacts cache dependencies before_script
14
+ after_script variables environment coverage retry].freeze
15
+
16
+ validations do
17
+ validates :config, allowed_keys: ALLOWED_KEYS
18
+ validates :config, presence: true
19
+ validates :script, presence: true
20
+ validates :name, presence: true
21
+ validates :name, type: Symbol
22
+
23
+ with_options allow_nil: true do
24
+ validates :tags, array_of_strings: true
25
+ validates :allow_failure, boolean: true
26
+ validates :retry, numericality: { only_integer: true,
27
+ greater_than_or_equal_to: 0,
28
+ less_than_or_equal_to: 2 }
29
+ validates :when,
30
+ inclusion: { in: %w[on_success on_failure always manual],
31
+ message: 'should be on_success, on_failure, ' \
32
+ 'always or manual' }
33
+
34
+ validates :dependencies, array_of_strings: true
35
+ end
36
+ end
37
+
38
+ entry :before_script, Entry::Script,
39
+ description: 'Global before script overridden in this job.'
40
+
41
+ entry :script, Entry::Commands,
42
+ description: 'Commands that will be executed in this job.'
43
+
44
+ entry :stage, Entry::Stage,
45
+ description: 'Pipeline stage this job will be executed into.'
46
+
47
+ entry :type, Entry::Stage,
48
+ description: 'Deprecated: stage this job will be executed into.'
49
+
50
+ entry :after_script, Entry::Script,
51
+ description: 'Commands that will be executed when finishing job.'
52
+
53
+ entry :cache, Entry::Cache,
54
+ description: 'Cache definition for this job.'
55
+
56
+ entry :image, Entry::Image,
57
+ description: 'Image that will be used to execute this job.'
58
+
59
+ entry :services, Entry::Services,
60
+ description: 'Services that will be used to execute this job.'
61
+
62
+ entry :only, Entry::Policy,
63
+ description: 'Refs policy this job will be executed for.'
64
+
65
+ entry :except, Entry::Policy,
66
+ description: 'Refs policy this job will be executed for.'
67
+
68
+ entry :variables, Entry::Variables,
69
+ description: 'Environment variables available for this job.'
70
+
71
+ entry :artifacts, Entry::Artifacts,
72
+ description: 'Artifacts configuration for this job.'
73
+
74
+ entry :environment, Entry::Environment,
75
+ description: 'Environment configuration for this job.'
76
+
77
+ entry :coverage, Entry::Coverage,
78
+ description: 'Coverage configuration for this job.'
79
+
80
+ helpers :before_script, :script, :stage, :type, :after_script,
81
+ :cache, :image, :services, :only, :except, :variables,
82
+ :artifacts, :commands, :environment, :coverage, :retry
83
+
84
+ attributes :script, :tags, :allow_failure, :when, :dependencies, :retry
85
+
86
+ def compose!(deps = nil)
87
+ super do
88
+ if type_defined? && !stage_defined?
89
+ @entries[:stage] = @entries[:type]
90
+ end
91
+
92
+ @entries.delete(:type)
93
+ end
94
+
95
+ inherit!(deps)
96
+ end
97
+
98
+ def name
99
+ @metadata[:name]
100
+ end
101
+
102
+ def value
103
+ @config.merge(to_hash.compact)
104
+ end
105
+
106
+ def commands
107
+ (before_script_value.to_a + script_value.to_a).join("\n")
108
+ end
109
+
110
+ def manual_action?
111
+ self.when == 'manual'
112
+ end
113
+
114
+ def ignored?
115
+ allow_failure.nil? ? manual_action? : allow_failure
116
+ end
117
+
118
+ private
119
+
120
+ def inherit!(deps)
121
+ return unless deps
122
+
123
+ self.class.nodes.each_key do |key|
124
+ global_entry = deps[key]
125
+ job_entry = self[key]
126
+
127
+ if global_entry.specified? && !job_entry.specified?
128
+ @entries[key] = global_entry
129
+ end
130
+ end
131
+ end
132
+
133
+ def to_hash
134
+ { name: name,
135
+ before_script: before_script_value,
136
+ script: script_value,
137
+ commands: commands,
138
+ image: image_value,
139
+ services: services_value,
140
+ stage: stage_value,
141
+ cache: cache_value,
142
+ only: only_value,
143
+ except: except_value,
144
+ variables: variables_defined? ? variables_value : nil,
145
+ environment: environment_defined? ? environment_value : nil,
146
+ environment_name: environment_defined? ? environment_value[:name] : nil,
147
+ coverage: coverage_defined? ? coverage_value : nil,
148
+ retry: retry_defined? ? retry_value.to_i : nil,
149
+ artifacts: artifacts_value,
150
+ after_script: after_script_value,
151
+ ignore: ignored? }
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end