puppet-check 2.2.0 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +19 -13
- data/lib/puppet-check/cli.rb +6 -4
- data/lib/puppet-check/data_parser.rb +24 -18
- data/lib/puppet-check/output_results.rb +36 -30
- data/lib/puppet-check/puppet_parser.rb +18 -16
- data/lib/puppet-check/rspec_puppet_support.rb +1 -1
- data/lib/puppet-check/ruby_parser.rb +27 -20
- data/lib/puppet-check/tasks.rb +6 -5
- data/lib/puppet_check.rb +28 -23
- data/spec/puppet-check/cli_spec.rb +1 -1
- data/spec/puppet-check/data_parser_spec.rb +60 -53
- data/spec/puppet-check/output_results_spec.rb +28 -39
- data/spec/puppet-check/puppet_parser_spec.rb +52 -42
- data/spec/puppet-check/rspec_puppet_support_spec.rb +4 -4
- data/spec/puppet-check/ruby_parser_spec.rb +52 -43
- data/spec/puppet-check/tasks_spec.rb +4 -8
- data/spec/puppet_check_spec.rb +63 -25
- data/spec/system/system_spec.rb +20 -20
- metadata +13 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 384ccc25e3b1a78f69e3d017131eaf9583dc24d73f1f9ef1c37c21ac706715b9
|
4
|
+
data.tar.gz: 5094bc3014d74b9a5707fade543d1535d4ef12684d5adaddbb4ceb26c5a4a37e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1bfc1834fc8dc70defbfce8d330f724b834653e5ba48d4a546ff3d93b3bf94d5ac68850d84dcca6d0ac1bad1990d30c07f58c5bfbaaaf2b123fa352c408200f8
|
7
|
+
data.tar.gz: e65f3c64cbc715d2e49c15378165256c17c01e440c979557908bd8a64fa5b77fdf74f8dac5fadbafec6df9b106bf05adb23443b552334759812850e3d89a872f
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -173,18 +173,24 @@ rake puppetcheck:kitchen:* # Execute Test Kitchen acceptance tests
|
|
173
173
|
You can add style, smoke, and regression checks to the `rake puppetcheck:file`, or change the output format, by adding the following after the require:
|
174
174
|
|
175
175
|
```ruby
|
176
|
-
# example of modifying Puppet Check behavior
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
176
|
+
# example of modifying Puppet Check behavior and creating a custom task
|
177
|
+
settings = {}
|
178
|
+
settings[:fail_on_warnings] = true # default false
|
179
|
+
settings[:style] = true # default false
|
180
|
+
settings[:smoke] = true # default false
|
181
|
+
settings[:regression] = true # in progress, do not use; default false
|
182
|
+
settings[:public] = 'public.pem' # default nil
|
183
|
+
settings[:private] = 'private.pem' # default nil
|
184
|
+
settings[:output_format] = 'yaml' # also 'json'; default 'text'
|
185
|
+
settings[:octoconfig] = '$HOME/octocatalog-diff.cfg.rb' # default '.octocatalog-diff.cfg.rb'
|
186
|
+
settings[:octonodes] = %w(server.example.com) # default: %w(localhost.localdomain)
|
187
|
+
settings[:puppetlint_args] = ['--puppetlint-arg-one', '--puppetlint-arg-two'] # default []
|
188
|
+
settings[:rubocop_args] = ['--except', 'rubocop-arg-one,rubocop-arg-two'] # default []
|
189
|
+
|
190
|
+
desc 'Execute custom Puppet-Check file checks'
|
191
|
+
task :file_custom do
|
192
|
+
Rake::Task[:'puppetcheck:file'].invoke(settings)
|
193
|
+
end
|
188
194
|
```
|
189
195
|
|
190
196
|
Please note that `rspec` does not support yaml output and therefore would still use the default 'progress' formatter even if `yaml` is specified as the format option to Puppet Check.
|
@@ -276,7 +282,7 @@ PuppetCheck.new.run(settings, [dirs, files])
|
|
276
282
|
require 'puppet-check/rspec_puppet_support'
|
277
283
|
|
278
284
|
RSpecPuppetSupport.run
|
279
|
-
task.pattern = Dir.glob('**/{classes,defines,facter,functions,hosts,puppet,unit,types}/**/*_spec.rb').
|
285
|
+
task.pattern = Dir.glob('**/{classes,defines,facter,functions,hosts,puppet,unit,types}/**/*_spec.rb').grep_v(/fixtures/)
|
280
286
|
```
|
281
287
|
|
282
288
|
### Docker
|
data/lib/puppet-check/cli.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'optparse'
|
2
1
|
require_relative '../puppet_check'
|
3
2
|
|
4
3
|
# the command line interface for PuppetCheck
|
@@ -7,7 +6,7 @@ class PuppetCheck::CLI
|
|
7
6
|
def self.run(args)
|
8
7
|
# gather the user arguments
|
9
8
|
settings = parse(args)
|
10
|
-
raise 'puppet-check: no paths specified; try using --help' if args.empty?
|
9
|
+
raise 'puppet-check: no file paths specified; try using --help' if args.empty?
|
11
10
|
|
12
11
|
# run PuppetCheck with specified paths
|
13
12
|
PuppetCheck.new.run(settings, args)
|
@@ -15,6 +14,9 @@ class PuppetCheck::CLI
|
|
15
14
|
|
16
15
|
# parse the user arguments
|
17
16
|
def self.parse(args)
|
17
|
+
private_class_method :method
|
18
|
+
require 'optparse'
|
19
|
+
|
18
20
|
# show help message if no args specified
|
19
21
|
args = %w[-h] if args.empty?
|
20
22
|
|
@@ -27,7 +29,7 @@ class PuppetCheck::CLI
|
|
27
29
|
|
28
30
|
# base options
|
29
31
|
opts.on('--version', 'Display the current version.') do
|
30
|
-
puts 'puppet-check 2.2.
|
32
|
+
puts 'puppet-check 2.2.1'
|
31
33
|
exit 0
|
32
34
|
end
|
33
35
|
|
@@ -58,7 +60,7 @@ class PuppetCheck::CLI
|
|
58
60
|
opts.on('--rubocop arg_one,arg_two', String, 'Arguments for Rubocop disabled cops') { |arg| settings[:rubocop_args] = ['--except', arg] }
|
59
61
|
end
|
60
62
|
|
61
|
-
# remove
|
63
|
+
# remove matched args and return settings
|
62
64
|
opt_parser.parse!(args)
|
63
65
|
settings
|
64
66
|
end
|
@@ -2,23 +2,23 @@ require_relative '../puppet_check'
|
|
2
2
|
|
3
3
|
# executes diagnostics on data files
|
4
4
|
class DataParser
|
5
|
+
require 'yaml'
|
6
|
+
|
5
7
|
# checks yaml (.yaml/.yml)
|
6
8
|
def self.yaml(files)
|
7
|
-
require 'yaml'
|
8
|
-
|
9
9
|
files.each do |file|
|
10
10
|
# check yaml syntax
|
11
11
|
parsed = YAML.load_file(file)
|
12
12
|
rescue StandardError => err
|
13
|
-
PuppetCheck.
|
13
|
+
PuppetCheck.files[:errors][file] = err.to_s.gsub("(#{file}): ", '').split("\n")
|
14
14
|
else
|
15
15
|
warnings = []
|
16
16
|
|
17
17
|
# perform some rudimentary hiera checks if data exists and is hieradata
|
18
18
|
warnings = hiera(parsed, file) if parsed && (File.basename(file) != 'hiera.yaml')
|
19
19
|
|
20
|
-
next PuppetCheck.
|
21
|
-
PuppetCheck.
|
20
|
+
next PuppetCheck.files[:warnings][file] = warnings unless warnings.empty?
|
21
|
+
PuppetCheck.files[:clean].push(file.to_s)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -28,13 +28,13 @@ class DataParser
|
|
28
28
|
|
29
29
|
# keys specified?
|
30
30
|
if public.nil? || private.nil?
|
31
|
-
PuppetCheck.
|
31
|
+
PuppetCheck.files[:ignored].concat(files)
|
32
32
|
return warn 'Public X509 and/or Private RSA PKCS7 certs were not specified. EYAML checks will not be executed.'
|
33
33
|
end
|
34
34
|
|
35
35
|
# keys exist?
|
36
36
|
unless File.file?(public) && File.file?(private)
|
37
|
-
PuppetCheck.
|
37
|
+
PuppetCheck.files[:ignored].concat(files)
|
38
38
|
return warn 'Specified Public X509 and/or Private RSA PKCS7 certs do not exist. EYAML checks will not be executed.'
|
39
39
|
end
|
40
40
|
|
@@ -54,19 +54,25 @@ class DataParser
|
|
54
54
|
begin
|
55
55
|
parsed = YAML.load_file(decrypted)
|
56
56
|
rescue StandardError => err
|
57
|
-
PuppetCheck.
|
57
|
+
PuppetCheck.files[:errors][file] = err.to_s.gsub("(#{file}): ", '').split("\n")
|
58
58
|
else
|
59
59
|
warnings = []
|
60
60
|
|
61
61
|
# perform some rudimentary hiera checks if data exists and is hieradata
|
62
62
|
warnings = hiera(parsed, file) if parsed
|
63
63
|
|
64
|
-
next PuppetCheck.
|
65
|
-
PuppetCheck.
|
64
|
+
next PuppetCheck.files[:warnings][file] = warnings unless warnings.empty?
|
65
|
+
PuppetCheck.files[:clean].push(file.to_s)
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
+
# metadata consts
|
71
|
+
REQUIRED_KEYS = %w[name version author license summary source dependencies].freeze
|
72
|
+
REQ_DEP_KEYS = %w[requirements dependencies].freeze
|
73
|
+
DEPRECATED_KEYS = %w[types checksum].freeze
|
74
|
+
TASK_INPUTS = %w[environment stdin powershell].freeze
|
75
|
+
|
70
76
|
# checks json (.json)
|
71
77
|
def self.json(files)
|
72
78
|
require 'json'
|
@@ -76,7 +82,7 @@ class DataParser
|
|
76
82
|
begin
|
77
83
|
parsed = JSON.parse(File.read(file))
|
78
84
|
rescue JSON::ParserError => err
|
79
|
-
PuppetCheck.
|
85
|
+
PuppetCheck.files[:errors][file] = err.to_s.lines.first.strip.split("\n")
|
80
86
|
else
|
81
87
|
warnings = []
|
82
88
|
|
@@ -89,12 +95,12 @@ class DataParser
|
|
89
95
|
errors = []
|
90
96
|
|
91
97
|
# check for required keys
|
92
|
-
|
98
|
+
REQUIRED_KEYS.each do |key|
|
93
99
|
errors.push("Required field '#{key}' not found.") unless parsed.key?(key)
|
94
100
|
end
|
95
101
|
|
96
102
|
# check requirements and dependencies keys
|
97
|
-
|
103
|
+
REQ_DEP_KEYS.each do |key|
|
98
104
|
# skip if key is missing or value is an empty string, array, or hash
|
99
105
|
next if !parsed.key?(key) || parsed[key].empty?
|
100
106
|
|
@@ -123,14 +129,14 @@ class DataParser
|
|
123
129
|
end
|
124
130
|
|
125
131
|
# check for deprecated fields
|
126
|
-
|
132
|
+
DEPRECATED_KEYS.each do |key|
|
127
133
|
errors.push("Deprecated field '#{key}' found.") if parsed.key?(key)
|
128
134
|
end
|
129
135
|
|
130
136
|
# check for summary under 144 character
|
131
137
|
errors.push('Summary exceeds 144 characters.') if parsed.key?('summary') && parsed['summary'].size > 144
|
132
138
|
|
133
|
-
next PuppetCheck.
|
139
|
+
next PuppetCheck.files[:errors][file] = errors unless errors.empty?
|
134
140
|
|
135
141
|
# check for warnings
|
136
142
|
# check for operatingsystem_support hash array
|
@@ -169,7 +175,7 @@ class DataParser
|
|
169
175
|
# check that input_method is one of three possible values
|
170
176
|
if parsed.key?('input_method')
|
171
177
|
if parsed['input_method'].is_a?(String)
|
172
|
-
warnings.push('input_method value is not one of environment, stdin, or powershell') unless
|
178
|
+
warnings.push('input_method value is not one of environment, stdin, or powershell') unless TASK_INPUTS.include?(parsed['input_method'])
|
173
179
|
else
|
174
180
|
warnings.push('input_method value is not a String')
|
175
181
|
end
|
@@ -191,8 +197,8 @@ class DataParser
|
|
191
197
|
# perform some rudimentary hiera checks if data exists
|
192
198
|
warnings = hiera(parsed, file)
|
193
199
|
end
|
194
|
-
next PuppetCheck.
|
195
|
-
PuppetCheck.
|
200
|
+
next PuppetCheck.files[:warnings][file] = warnings unless warnings.empty?
|
201
|
+
PuppetCheck.files[:clean].push(file.to_s)
|
196
202
|
end
|
197
203
|
end
|
198
204
|
end
|
@@ -2,44 +2,50 @@ require_relative '../puppet_check'
|
|
2
2
|
|
3
3
|
# class to handle outputting diagnostic results in desired format
|
4
4
|
class OutputResults
|
5
|
-
# output the results
|
6
|
-
def self.
|
7
|
-
|
8
|
-
|
9
|
-
puts PuppetCheck.settings[:error_files].join("\n\n-- ")
|
10
|
-
end
|
11
|
-
unless PuppetCheck.settings[:warning_files].empty?
|
12
|
-
print "\n\033[33mThe following files have warnings:\033[0m\n-- "
|
13
|
-
puts PuppetCheck.settings[:warning_files].join("\n\n-- ")
|
14
|
-
end
|
15
|
-
unless PuppetCheck.settings[:clean_files].empty?
|
16
|
-
print "\n\033[32mThe following files have no errors or warnings:\033[0m\n-- "
|
17
|
-
puts PuppetCheck.settings[:clean_files].join("\n-- ")
|
18
|
-
end
|
19
|
-
return if PuppetCheck.settings[:ignored_files].empty?
|
20
|
-
print "\n\033[36mThe following files have unrecognized formats and therefore were not processed:\033[0m\n-- "
|
21
|
-
puts PuppetCheck.settings[:ignored_files].join("\n-- ")
|
22
|
-
end
|
5
|
+
# output the results in various formats
|
6
|
+
def self.run(files, format)
|
7
|
+
# remove empty entries
|
8
|
+
files.delete_if { |_, sorted_files| sorted_files.empty? }
|
23
9
|
|
24
|
-
|
25
|
-
def self.markup(format)
|
26
|
-
# generate output hash
|
27
|
-
hash = {}
|
28
|
-
hash['errors'] = PuppetCheck.settings[:error_files] unless PuppetCheck.settings[:error_files].empty?
|
29
|
-
hash['warnings'] = PuppetCheck.settings[:warning_files] unless PuppetCheck.settings[:warning_files].empty?
|
30
|
-
hash['clean'] = PuppetCheck.settings[:clean_files] unless PuppetCheck.settings[:clean_files].empty?
|
31
|
-
hash['ignored'] = PuppetCheck.settings[:ignored_files] unless PuppetCheck.settings[:ignored_files].empty?
|
32
|
-
|
33
|
-
# convert hash to markup language
|
10
|
+
# output hash according to specified format
|
34
11
|
case format
|
12
|
+
when 'text'
|
13
|
+
text(files)
|
35
14
|
when 'yaml'
|
36
15
|
require 'yaml'
|
37
|
-
|
16
|
+
# maintain filename format consistency among output formats
|
17
|
+
files.transform_keys!(&:to_s)
|
18
|
+
puts Psych.dump(files, indentation: 2)
|
38
19
|
when 'json'
|
39
20
|
require 'json'
|
40
|
-
puts JSON.pretty_generate(
|
21
|
+
puts JSON.pretty_generate(files)
|
41
22
|
else
|
42
23
|
raise "puppet-check: Unsupported output format '#{format}' was specified."
|
43
24
|
end
|
44
25
|
end
|
26
|
+
|
27
|
+
# output the results as text
|
28
|
+
def self.text(files)
|
29
|
+
private_class_method :method
|
30
|
+
|
31
|
+
# errors
|
32
|
+
if files.key?(:errors)
|
33
|
+
puts "\033[31mThe following files have errors:\033[0m"
|
34
|
+
files[:errors].each { |file, errors| puts "-- #{file}:\n#{errors.join("\n")}" }
|
35
|
+
end
|
36
|
+
# warnings
|
37
|
+
if files.key?(:warnings)
|
38
|
+
puts "\n\033[33mThe following files have warnings:\033[0m"
|
39
|
+
files[:warnings].each { |file, warnings| puts "-- #{file}:\n#{warnings.join("\n")}" }
|
40
|
+
end
|
41
|
+
# cleans
|
42
|
+
if files.key?(:clean)
|
43
|
+
print "\n\033[32mThe following files have no errors or warnings:\033[0m\n-- "
|
44
|
+
puts files[:clean].join("\n-- ")
|
45
|
+
end
|
46
|
+
# ignores
|
47
|
+
return unless files.key?(:ignored)
|
48
|
+
print "\n\033[36mThe following files have unrecognized formats and therefore were not processed:\033[0m\n-- "
|
49
|
+
puts files[:ignored].join("\n-- ")
|
50
|
+
end
|
45
51
|
end
|
@@ -18,30 +18,33 @@ class PuppetParser
|
|
18
18
|
# check puppet syntax
|
19
19
|
begin
|
20
20
|
# initialize message
|
21
|
-
|
21
|
+
messages = []
|
22
22
|
# specify tasks attribute for parser validation if this looks like a plan or not
|
23
|
-
Puppet[:tasks] = file.match?(%r{plans/\w+\.pp$})
|
23
|
+
Puppet[:tasks] = file.match?(%r{plans/\w+\.pp$})
|
24
24
|
# in puppet >= 6.5 the return of this method is a hash with the error
|
25
25
|
new_error = Puppet::Face[:parser, :current].validate(file)
|
26
26
|
# puppet 6.5 output format is now a hash from the face api
|
27
27
|
if Gem::Version.new(Puppet::PUPPETVERSION) >= Gem::Version.new('6.5.0') && new_error != {}
|
28
|
-
|
28
|
+
messages.concat(new_error.values.map(&:to_s).map { |error| error.gsub(/ \(file: #{File.absolute_path(file)}(, |\))/, '') }.map { |error| error.gsub('Could not parse for environment *root*: ', '') })
|
29
29
|
end
|
30
30
|
# this is the actual error that we need to rescue Puppet::Face from
|
31
31
|
rescue SystemExit
|
32
32
|
# puppet 5.4-6.4 has a new validator output format and eof errors have fake dir env info
|
33
|
-
|
33
|
+
messages.concat(errors.map(&:to_s).join("\n").map { |error| error.gsub(/file: #{File.absolute_path(file)}(, |\))/, '') }.map { |error| error.gsub(/Could not parse.*: /, '') })
|
34
34
|
end
|
35
|
-
|
36
|
-
|
35
|
+
|
36
|
+
Puppet::Util::Log.close_all
|
37
|
+
|
38
|
+
# store info and continue validating files
|
39
|
+
next PuppetCheck.files[:errors][file] = messages unless messages.empty?
|
37
40
|
|
38
41
|
# initialize warnings with output from the parser if it exists, since the output is warnings if Puppet::Face did not trigger a SystemExit
|
39
|
-
warnings =
|
42
|
+
warnings = []
|
43
|
+
# weirdly puppet >= 6.5 still does not return warnings and logs them instead unlike errors
|
40
44
|
unless errors.empty?
|
41
45
|
# puppet >= 5.4 has a new validator output format
|
42
|
-
warnings
|
46
|
+
warnings.concat(errors.map(&:to_s).join("\n").gsub("file: #{File.absolute_path(file)}, ", '').split("\n"))
|
43
47
|
end
|
44
|
-
Puppet::Util::Log.close_all
|
45
48
|
|
46
49
|
# check puppet style
|
47
50
|
if style
|
@@ -61,12 +64,11 @@ class PuppetParser
|
|
61
64
|
puppet_lint.run
|
62
65
|
|
63
66
|
# collect the warnings
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
+
offenses = puppet_lint.problems.map { |problem| "#{problem[:line]}:#{problem[:column]} #{problem[:message]}" }
|
68
|
+
warnings.concat(offenses)
|
67
69
|
end
|
68
|
-
next PuppetCheck.
|
69
|
-
PuppetCheck.
|
70
|
+
next PuppetCheck.files[:warnings][file] = warnings unless warnings.empty?
|
71
|
+
PuppetCheck.files[:clean].push(file.to_s)
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
@@ -79,9 +81,9 @@ class PuppetParser
|
|
79
81
|
# credits to gds-operations/puppet-syntax for the parser function call
|
80
82
|
Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new.parse_file(file)
|
81
83
|
rescue StandardError => err
|
82
|
-
PuppetCheck.
|
84
|
+
PuppetCheck.files[:errors][file] = [err.to_s.gsub("file: #{file}, ", '')]
|
83
85
|
else
|
84
|
-
PuppetCheck.
|
86
|
+
PuppetCheck.files[:clean].push(file.to_s)
|
85
87
|
end
|
86
88
|
end
|
87
89
|
end
|
@@ -9,7 +9,7 @@ class RSpecPuppetSupport
|
|
9
9
|
# prepare the spec fixtures directory for rspec-puppet testing
|
10
10
|
def self.run
|
11
11
|
# ensure this method does not do anything inside module dependencies
|
12
|
-
specdirs = Dir.glob('**/spec').reject { |dir| dir
|
12
|
+
specdirs = Dir.glob('**/spec').reject { |dir| dir.include?('fixtures') }
|
13
13
|
return if specdirs.empty?
|
14
14
|
|
15
15
|
# setup fixtures for rspec-puppet testing
|
@@ -10,26 +10,31 @@ class RubyParser
|
|
10
10
|
# prevents ruby code from actually executing
|
11
11
|
catch(:good) { instance_eval("BEGIN {throw :good}; #{File.read(file)}", file) }
|
12
12
|
rescue ScriptError, StandardError => err
|
13
|
-
PuppetCheck.
|
13
|
+
PuppetCheck.files[:errors][file] = err.to_s.gsub("#{file}:", '').split("\n")
|
14
14
|
else
|
15
15
|
# check ruby style
|
16
16
|
if style
|
17
|
+
# check RuboCop and parse warnings' JSON output
|
18
|
+
require 'json'
|
17
19
|
require 'rubocop'
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
warnings = rubocop_warnings == '' ? '' : rubocop_warnings.split("#{File.absolute_path(file)}:").join('')
|
21
|
+
rubocop_warnings = Utils.capture_stdout { RuboCop::CLI.new.run(rc_args + ['--enable-pending-cops', '--require', 'rubocop-performance', '--format', 'json', file]) }
|
22
|
+
rubocop_offenses = JSON.parse(rubocop_warnings)['files'][0]['offenses'].map { |warning| "#{warning['location']['line']}:#{warning['location']['column']} #{warning['message']}" }
|
22
23
|
|
23
|
-
# check Reek and
|
24
|
+
# check Reek and parse warnings' JSON output
|
24
25
|
require 'reek'
|
25
26
|
require 'reek/cli/application'
|
26
|
-
|
27
|
-
|
27
|
+
|
28
|
+
reek_warnings = Utils.capture_stdout { Reek::CLI::Application.new(['-f', 'json', file]).execute }
|
29
|
+
reek_offenses = JSON.parse(reek_warnings).map { |warning| "#{warning['lines'].join(',')}: #{warning['context']} #{warning['message']}" }
|
30
|
+
|
31
|
+
# assign warnings from combined offenses
|
32
|
+
warnings = rubocop_offenses + reek_offenses
|
28
33
|
|
29
34
|
# return warnings
|
30
|
-
next PuppetCheck.
|
35
|
+
next PuppetCheck.files[:warnings][file] = warnings unless warnings.empty?
|
31
36
|
end
|
32
|
-
PuppetCheck.
|
37
|
+
PuppetCheck.files[:clean].push(file.to_s)
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
@@ -43,16 +48,16 @@ class RubyParser
|
|
43
48
|
# need to eventually have this associated with a different binding during each iteration
|
44
49
|
# older usage throws extra warning and mixes with valid warnings confusingly
|
45
50
|
warnings = Utils.capture_stderr { ERB.new(File.read(file), trim_mode: '-').result }
|
46
|
-
|
51
|
+
# warnings = ERB.new(File.read(file), trim_mode: '-').result(RubyParser.new.bind)
|
47
52
|
rescue NameError, TypeError
|
48
53
|
# empty out warnings since it would contain an error if this pass triggers
|
49
54
|
warnings = ''
|
50
55
|
rescue ScriptError => err
|
51
|
-
next PuppetCheck.
|
56
|
+
next PuppetCheck.files[:errors][file] = err.to_s.gsub('(erb):', '').split("\n")
|
52
57
|
end
|
53
58
|
# return warnings from the check if there were any
|
54
|
-
next PuppetCheck.
|
55
|
-
PuppetCheck.
|
59
|
+
next PuppetCheck.files[:warnings][file] = warnings.to_s.gsub('warning: ', '').delete("\n").split('(erb):').compact unless warnings == ''
|
60
|
+
PuppetCheck.files[:clean].push(file.to_s)
|
56
61
|
end
|
57
62
|
end
|
58
63
|
|
@@ -60,10 +65,8 @@ class RubyParser
|
|
60
65
|
def self.librarian(files, style, rc_args)
|
61
66
|
# efficient var assignment prior to iterator
|
62
67
|
if style
|
63
|
-
require 'rubocop'
|
64
|
-
|
65
68
|
# RuboCop is grumpy about non-snake_case filenames so disable the FileName check
|
66
|
-
rc_args.include?('--except') ? rc_args[rc_args.index('--except') + 1] = "#{rc_args[rc_args.index('--except') + 1]},Naming/FileName" : rc_args.
|
69
|
+
rc_args.include?('--except') ? rc_args[rc_args.index('--except') + 1] = "#{rc_args[rc_args.index('--except') + 1]},Naming/FileName" : rc_args.push('--except', 'Naming/FileName')
|
67
70
|
end
|
68
71
|
|
69
72
|
files.each do |file|
|
@@ -71,17 +74,21 @@ class RubyParser
|
|
71
74
|
# prevents ruby code from actually executing
|
72
75
|
catch(:good) { instance_eval("BEGIN {throw :good}; #{File.read(file)}", file) }
|
73
76
|
rescue SyntaxError, LoadError, ArgumentError => err
|
74
|
-
PuppetCheck.
|
77
|
+
PuppetCheck.files[:errors][file] = err.to_s.gsub("#{file}:", '').split("\n")
|
75
78
|
# check librarian puppet style
|
76
79
|
else
|
77
80
|
if style
|
78
81
|
# check Rubocop
|
79
|
-
|
82
|
+
require 'json'
|
83
|
+
require 'rubocop'
|
84
|
+
|
85
|
+
warnings = Utils.capture_stdout { RuboCop::CLI.new.run(rc_args + ['--enable-pending-cops', '--require', 'rubocop-performance', '--format', 'json', file]) }
|
86
|
+
offenses = JSON.parse(warnings)['files'][0]['offenses'].map { |warning| "#{warning['location']['line']}:#{warning['location']['column']} #{warning['message']}" }
|
80
87
|
|
81
88
|
# collect style warnings
|
82
|
-
next PuppetCheck.
|
89
|
+
next PuppetCheck.files[:warnings][file] = offenses unless offenses.empty?
|
83
90
|
end
|
84
|
-
PuppetCheck.
|
91
|
+
PuppetCheck.files[:clean].push(file.to_s)
|
85
92
|
end
|
86
93
|
end
|
87
94
|
|
data/lib/puppet-check/tasks.rb
CHANGED
@@ -6,15 +6,17 @@ end
|
|
6
6
|
require_relative '../puppet_check'
|
7
7
|
|
8
8
|
# the rake interface for PuppetCheck
|
9
|
-
class PuppetCheck::Tasks <
|
9
|
+
class PuppetCheck::Tasks < Rake::TaskLib
|
10
10
|
def initialize
|
11
|
+
super
|
12
|
+
|
11
13
|
desc 'Execute all Puppet-Check checks'
|
12
14
|
task puppetcheck: %w[puppetcheck:file puppetcheck:spec puppetcheck:beaker puppetcheck:kitchen]
|
13
15
|
|
14
16
|
namespace :puppetcheck do
|
15
17
|
desc 'Execute Puppet-Check file checks'
|
16
|
-
task :file do
|
17
|
-
PuppetCheck.new.run(
|
18
|
+
task :file, [:settings] do |_, wrapped_settings|
|
19
|
+
PuppetCheck.new.run(wrapped_settings[:settings], Dir.glob('*'))
|
18
20
|
end
|
19
21
|
|
20
22
|
# rspec and rspec-puppet tasks
|
@@ -26,9 +28,8 @@ class PuppetCheck::Tasks < ::Rake::TaskLib
|
|
26
28
|
RSpec::Core::RakeTask.new(:spec) do |task|
|
27
29
|
RSpecPuppetSupport.run
|
28
30
|
# generate tasks for all recognized directories and ensure spec tests inside module dependencies are ignored
|
29
|
-
spec_dirs = Dir.glob('**/{classes,defines,facter,functions,hosts,puppet,unit,types}/**/*_spec.rb').
|
31
|
+
spec_dirs = Dir.glob('**/{classes,defines,facter,functions,hosts,puppet,unit,types}/**/*_spec.rb').grep_v(/fixtures/)
|
30
32
|
task.pattern = spec_dirs.empty? ? 'skip_rspec' : spec_dirs
|
31
|
-
task.rspec_opts = '-f json' if PuppetCheck.settings[:output_format] == 'json'
|
32
33
|
end
|
33
34
|
rescue LoadError
|
34
35
|
desc 'RSpec is not installed.'
|
data/lib/puppet_check.rb
CHANGED
@@ -5,33 +5,35 @@ require_relative 'puppet-check/output_results'
|
|
5
5
|
|
6
6
|
# interfaces from CLI/tasks and to individual parsers
|
7
7
|
class PuppetCheck
|
8
|
-
# initialize
|
9
|
-
@
|
10
|
-
|
11
|
-
|
8
|
+
# initialize files hash
|
9
|
+
@files = {
|
10
|
+
errors: {},
|
11
|
+
warnings: {},
|
12
|
+
clean: [],
|
13
|
+
ignored: []
|
14
|
+
}
|
15
|
+
|
16
|
+
# allow the parser methods write to the files
|
12
17
|
class << self
|
13
|
-
attr_accessor :
|
18
|
+
attr_accessor :files
|
14
19
|
end
|
15
20
|
|
16
21
|
# main runner for PuppetCheck
|
17
|
-
def run(settings, paths)
|
18
|
-
# establish settings
|
19
|
-
self.class.settings = settings
|
20
|
-
|
22
|
+
def run(settings = {}, paths = [])
|
21
23
|
# settings defaults
|
22
|
-
self.class.defaults(settings)
|
24
|
+
settings = self.class.defaults(settings)
|
23
25
|
|
24
26
|
# grab all of the files to be processed
|
25
27
|
files = self.class.parse_paths(paths)
|
26
28
|
|
27
29
|
# parse the files
|
28
|
-
execute_parsers(files, settings)
|
30
|
+
parsed_files = execute_parsers(files, settings)
|
29
31
|
|
30
32
|
# output the diagnostic results
|
31
|
-
|
33
|
+
OutputResults.run(parsed_files, settings[:output_format])
|
32
34
|
|
33
35
|
# progress to regression checks if no errors in file checks
|
34
|
-
if self.class.
|
36
|
+
if self.class.files[:errors].empty? && (!settings[:fail_on_warning] || self.class.files[:warnings].empty?)
|
35
37
|
begin
|
36
38
|
require_relative 'puppet-check/regression_check'
|
37
39
|
# if octocatalog-diff is not installed then return immediately
|
@@ -68,7 +70,8 @@ class PuppetCheck
|
|
68
70
|
end
|
69
71
|
|
70
72
|
# establish default settings
|
71
|
-
def self.defaults(settings)
|
73
|
+
def self.defaults(settings = {})
|
74
|
+
private_class_method :method
|
72
75
|
# initialize fail on warning, style check, and regression check bools
|
73
76
|
settings[:fail_on_warning] ||= false
|
74
77
|
settings[:style] ||= false
|
@@ -82,12 +85,6 @@ class PuppetCheck
|
|
82
85
|
# initialize output format option
|
83
86
|
settings[:output_format] ||= 'text'
|
84
87
|
|
85
|
-
# initialize diagnostic output arrays
|
86
|
-
@settings[:error_files] = []
|
87
|
-
@settings[:warning_files] = []
|
88
|
-
@settings[:clean_files] = []
|
89
|
-
@settings[:ignored_files] = []
|
90
|
-
|
91
88
|
# initialize octocatalog-diff options
|
92
89
|
settings[:octoconfig] ||= '.octocatalog-diff.cfg.rb'
|
93
90
|
settings[:octonodes] ||= %w[localhost.localdomain]
|
@@ -95,10 +92,14 @@ class PuppetCheck
|
|
95
92
|
# initialize style arg arrays
|
96
93
|
settings[:puppetlint_args] ||= []
|
97
94
|
settings[:rubocop_args] ||= []
|
95
|
+
|
96
|
+
# return update settings
|
97
|
+
settings
|
98
98
|
end
|
99
99
|
|
100
100
|
# parse the paths and return the array of files
|
101
|
-
def self.parse_paths(paths)
|
101
|
+
def self.parse_paths(paths = [])
|
102
|
+
private_class_method :method
|
102
103
|
files = []
|
103
104
|
|
104
105
|
# traverse the unique paths and return all files
|
@@ -111,13 +112,15 @@ class PuppetCheck
|
|
111
112
|
end
|
112
113
|
|
113
114
|
# do not process fixtures, check that at least one file was found, and remove double slashes
|
114
|
-
files.reject! { |file| file
|
115
|
+
files.reject! { |file| file.include?('fixtures') }
|
115
116
|
raise "puppet-check: no files found in supplied paths '#{paths.join(', ')}'." if files.empty?
|
116
117
|
files.map! { |file| file.gsub('//', '/') }
|
117
118
|
|
118
119
|
files.uniq
|
119
120
|
end
|
120
121
|
|
122
|
+
private
|
123
|
+
|
121
124
|
# categorize and pass the files out to the parsers to determine their status
|
122
125
|
def execute_parsers(files, settings)
|
123
126
|
# check manifests
|
@@ -145,6 +148,8 @@ class PuppetCheck
|
|
145
148
|
librarians, files = files.partition { |file| File.basename(file) =~ /(?:Puppet|Module|Rake|Gem)file$/ }
|
146
149
|
RubyParser.librarian(librarians, settings[:style], settings[:rubocop_args]) unless librarians.empty?
|
147
150
|
# ignore everything else
|
148
|
-
self.class.
|
151
|
+
files.each { |file| self.class.files[:ignored].push(file.to_s) }
|
152
|
+
# return PuppetCheck.files to mitigate singleton write accessor side effects
|
153
|
+
PuppetCheck.files
|
149
154
|
end
|
150
155
|
end
|