puppet-check 1.0.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/.rubocop.yml +11 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +14 -0
- data/Dockerfile +22 -0
- data/Gemfile +5 -0
- data/LICENSE.md +20 -0
- data/README.md +148 -0
- data/Rakefile +24 -0
- data/Vagrantfile +77 -0
- data/bin/puppet-check +5 -0
- data/config.reek +3 -0
- data/images/puppetcheck_new.png +0 -0
- data/images/puppetcheck_old.odp +0 -0
- data/images/puppetcheck_old.png +0 -0
- data/lib/puppet-check.rb +86 -0
- data/lib/puppet-check/cli.rb +33 -0
- data/lib/puppet-check/data_parser.rb +95 -0
- data/lib/puppet-check/puppet_parser.rb +77 -0
- data/lib/puppet-check/ruby_parser.rb +93 -0
- data/lib/puppet-check/tasks.rb +15 -0
- data/lib/puppet-check/utils.rb +22 -0
- data/puppet-check.gemspec +25 -0
- data/spec/fixtures/foobarbaz +1 -0
- data/spec/fixtures/hieradata/good.json +7 -0
- data/spec/fixtures/hieradata/good.yaml +7 -0
- data/spec/fixtures/hieradata/style.yaml +15 -0
- data/spec/fixtures/hieradata/syntax.json +8 -0
- data/spec/fixtures/hieradata/syntax.yaml +8 -0
- data/spec/fixtures/lib/good.rb +1 -0
- data/spec/fixtures/lib/rubocop_style.rb +3 -0
- data/spec/fixtures/lib/style.rb +12 -0
- data/spec/fixtures/lib/syntax.rb +1 -0
- data/spec/fixtures/librarian_good/Puppetfile +5 -0
- data/spec/fixtures/librarian_style/Puppetfile +5 -0
- data/spec/fixtures/librarian_syntax/Puppetfile +7 -0
- data/spec/fixtures/manifests/good.pp +1 -0
- data/spec/fixtures/manifests/style_lint.pp +4 -0
- data/spec/fixtures/manifests/style_parser.pp +2 -0
- data/spec/fixtures/manifests/syntax.pp +1 -0
- data/spec/fixtures/metadata_good/metadata.json +35 -0
- data/spec/fixtures/metadata_style/metadata.json +35 -0
- data/spec/fixtures/metadata_syntax/metadata.json +15 -0
- data/spec/fixtures/templates/good.epp +3 -0
- data/spec/fixtures/templates/good.erb +1 -0
- data/spec/fixtures/templates/no_method_error.erb +3 -0
- data/spec/fixtures/templates/style.erb +3 -0
- data/spec/fixtures/templates/syntax.epp +3 -0
- data/spec/fixtures/templates/syntax.erb +1 -0
- data/spec/integration/integration_spec.rb +41 -0
- data/spec/puppet-check/cli_spec.rb +48 -0
- data/spec/puppet-check/data_parser_spec.rb +64 -0
- data/spec/puppet-check/puppet_parser_spec.rb +67 -0
- data/spec/puppet-check/ruby_parser_spec.rb +111 -0
- data/spec/puppet-check/utils_spec.rb +20 -0
- data/spec/puppet-check_spec.rb +82 -0
- data/spec/spec_helper.rb +13 -0
- metadata +233 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require_relative '../puppet-check'
|
3
|
+
|
4
|
+
# the command line interface for PuppetCheck
|
5
|
+
class PuppetCheck::CLI
|
6
|
+
# run method for the cli
|
7
|
+
def self.run(args)
|
8
|
+
parse(args)
|
9
|
+
raise 'puppet-check: no paths specified' if args.empty?
|
10
|
+
|
11
|
+
# run PuppetCheck
|
12
|
+
PuppetCheck.new.run(args)
|
13
|
+
end
|
14
|
+
|
15
|
+
# parse the user arguments
|
16
|
+
def self.parse(args)
|
17
|
+
opt_parser = OptionParser.new do |opts|
|
18
|
+
# usage
|
19
|
+
opts.banner = 'usage: puppet-check [options] paths'
|
20
|
+
|
21
|
+
# bool options
|
22
|
+
opts.on('-f', '--future', 'Enable future parser') { PuppetCheck.future_parser = true }
|
23
|
+
opts.on('-s', '--style', 'Enable style checks') { PuppetCheck.style_check = true }
|
24
|
+
# arguments to style checkers
|
25
|
+
opts.on('--puppet-lint arg_one,arg_two', Array, 'Arguments for PuppetLint ignored checks') do |puppetlint_args|
|
26
|
+
PuppetCheck.puppetlint_args = puppetlint_args.map { |arg| "--#{arg}" }
|
27
|
+
end
|
28
|
+
opts.on('--rubocop arg_one,arg_two', String, 'Arguments for Rubocop disabled cops') { |arg| PuppetCheck.rubocop_args = ['--except', arg] }
|
29
|
+
end
|
30
|
+
|
31
|
+
opt_parser.parse!(args)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative '../puppet-check'
|
2
|
+
|
3
|
+
# executes diagnostics on data files
|
4
|
+
class DataParser
|
5
|
+
# checks yaml (.yaml/.yml)
|
6
|
+
def self.yaml(files)
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
files.each do |file|
|
10
|
+
# check yaml syntax
|
11
|
+
begin
|
12
|
+
parsed = YAML.load_file(file)
|
13
|
+
rescue Psych::SyntaxError, StandardError => err
|
14
|
+
PuppetCheck.error_files.push("-- #{file}:\n#{err.to_s.gsub("(#{file}): ", '')}")
|
15
|
+
else
|
16
|
+
warnings = hiera(parsed)
|
17
|
+
next PuppetCheck.warning_files.push("-- #{file}:\n#{warnings.join("\n")}") unless warnings.empty?
|
18
|
+
PuppetCheck.clean_files.push("-- #{file}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# checks json (.json)
|
24
|
+
def self.json(files)
|
25
|
+
require 'json'
|
26
|
+
|
27
|
+
files.each do |file|
|
28
|
+
# check json syntax
|
29
|
+
begin
|
30
|
+
parsed = JSON.parse(File.read(file))
|
31
|
+
rescue JSON::ParserError => err
|
32
|
+
PuppetCheck.error_files.push("-- #{file}:\n#{err.to_s.lines.first.strip}")
|
33
|
+
else
|
34
|
+
# check metadata.json
|
35
|
+
if file =~ /.*metadata\.json$/
|
36
|
+
# metadata-json-lint has issues and is essentially no longer maintained so here is an improved and leaner version of it
|
37
|
+
require 'spdx-licenses'
|
38
|
+
|
39
|
+
# check for errors
|
40
|
+
errors = []
|
41
|
+
|
42
|
+
# check for required keys
|
43
|
+
%w(name version author license summary source dependencies).each do |key|
|
44
|
+
errors.push("Required field '#{key}' not found in metadata.json.") unless parsed.key?(key)
|
45
|
+
end
|
46
|
+
|
47
|
+
# check for duplicate dependencies and requirements
|
48
|
+
%w(requirements dependencies).each do |key|
|
49
|
+
next unless parsed.key?(key)
|
50
|
+
names = []
|
51
|
+
parsed[key].each do |req_dep|
|
52
|
+
name = req_dep['name']
|
53
|
+
errors.push("Duplicate #{key} on #{name}.") if names.include?(name)
|
54
|
+
names << name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# check for deprecated fields
|
59
|
+
%w(types checksum).each do |key|
|
60
|
+
errors.push("Deprecated field '#{key}' found.") if parsed.key?(key)
|
61
|
+
end
|
62
|
+
|
63
|
+
# check for summary under 144 character
|
64
|
+
errors.push('Summary exceeds 144 characters.') if parsed.key?('summary') && parsed['summary'].size > 144
|
65
|
+
|
66
|
+
next PuppetCheck.error_files.push("-- #{file}:\n#{errors.join("\n")}") unless errors.empty?
|
67
|
+
|
68
|
+
# check for warnings
|
69
|
+
warnings = []
|
70
|
+
|
71
|
+
# check for spdx license (rubygems/util/licenses for rubygems >= 2.5 in the far future)
|
72
|
+
if parsed.key?('license') && !SpdxLicenses.exist?(parsed['license']) && parsed['license'] !~ /[pP]roprietary/
|
73
|
+
warnings.push("License identifier '#{parsed['license']}' is not in the SPDX list: http://spdx.org/licenses/")
|
74
|
+
end
|
75
|
+
|
76
|
+
next PuppetCheck.warning_files.push("-- #{file}:\n#{warnings.join("\n")}") unless warnings.empty?
|
77
|
+
else
|
78
|
+
# check for questionable hieradata
|
79
|
+
warnings = hiera(parsed)
|
80
|
+
next PuppetCheck.warning_files.push("-- #{file}:\n#{warnings.join("\n")}") unless warnings.empty?
|
81
|
+
end
|
82
|
+
PuppetCheck.clean_files.push("-- #{file}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# checks hieradata
|
88
|
+
def self.hiera(data)
|
89
|
+
warnings = []
|
90
|
+
data.each do |key, value|
|
91
|
+
warnings.push("Values missing in key '#{key}'.") if value.class.to_s == 'NilClass'
|
92
|
+
end
|
93
|
+
warnings
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'puppet'
|
2
|
+
require_relative '../puppet-check'
|
3
|
+
|
4
|
+
# executes diagnostics on puppet files
|
5
|
+
class PuppetParser
|
6
|
+
# checks puppet (.pp)
|
7
|
+
def self.manifest(files)
|
8
|
+
require 'puppet/face'
|
9
|
+
|
10
|
+
# prepare the Puppet settings for the error checking
|
11
|
+
Puppet.initialize_settings unless Puppet.settings.app_defaults_initialized?
|
12
|
+
Puppet[:parser] = 'future' if PuppetCheck.future_parser && (Puppet::PUPPETVERSION.to_i < 4)
|
13
|
+
|
14
|
+
files.each do |file|
|
15
|
+
# setup error logging and collection
|
16
|
+
errors = []
|
17
|
+
Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(errors))
|
18
|
+
|
19
|
+
# check puppet syntax
|
20
|
+
begin
|
21
|
+
Puppet::Face[:parser, :current].validate(file)
|
22
|
+
# this is the actual error that we need to rescue Puppet::Face from
|
23
|
+
rescue SystemExit
|
24
|
+
next PuppetCheck.error_files.push("-- #{file}:\n#{errors.map(&:to_s).join("\n").gsub("#{File.absolute_path(file)}:", '')}")
|
25
|
+
end
|
26
|
+
|
27
|
+
# initialize warnings with output from the parser if it exists since they are warnings if they did not trigger a SystemExit
|
28
|
+
warnings = errors.empty? ? "-- #{file}:" : "-- #{file}:\n#{errors.map(&:to_s).join("\n").gsub("#{File.absolute_path(file)}:", '')}"
|
29
|
+
Puppet::Util::Log.close_all
|
30
|
+
|
31
|
+
# check puppet style
|
32
|
+
if PuppetCheck.style_check
|
33
|
+
require 'puppet-lint'
|
34
|
+
require 'puppet-lint/optparser'
|
35
|
+
|
36
|
+
# check for invalid arguments to PuppetLint
|
37
|
+
begin
|
38
|
+
PuppetLint::OptParser.build.parse!(PuppetCheck.puppetlint_args)
|
39
|
+
rescue OptionParser::InvalidOption
|
40
|
+
raise 'puppet-lint: invalid option'
|
41
|
+
end
|
42
|
+
|
43
|
+
# prepare the PuppetLint object for style checks
|
44
|
+
puppet_lint = PuppetLint.new
|
45
|
+
puppet_lint.file = file
|
46
|
+
puppet_lint.run
|
47
|
+
|
48
|
+
# collect the warnings
|
49
|
+
if puppet_lint.warnings?
|
50
|
+
puppet_lint.problems.each { |values| warnings += "\n#{values[:message]} at line #{values[:line]}, column #{values[:column]}" }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
next PuppetCheck.warning_files.push(warnings) unless warnings == "-- #{file}:"
|
54
|
+
PuppetCheck.clean_files.push("-- #{file}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# checks puppet template (.epp)
|
59
|
+
def self.template(files)
|
60
|
+
require 'puppet/pops'
|
61
|
+
|
62
|
+
files.each do |file|
|
63
|
+
# puppet before version 4 cannot check template syntax
|
64
|
+
next PuppetCheck.ignored_files.push("-- #{file}: ignored due to Puppet Agent < 4.0.0") if Puppet::PUPPETVERSION.to_i < 4
|
65
|
+
|
66
|
+
# check puppet template syntax
|
67
|
+
begin
|
68
|
+
# credits to gds-operations/puppet-syntax for the parser function call
|
69
|
+
Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new.parse_file(file)
|
70
|
+
rescue StandardError => err
|
71
|
+
PuppetCheck.error_files.push("-- #{file}:\n#{err.to_s.gsub("#{file}:", '')}")
|
72
|
+
else
|
73
|
+
PuppetCheck.clean_files.push("-- #{file}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative '../puppet-check'
|
2
|
+
require_relative 'utils'
|
3
|
+
|
4
|
+
# executes diagnostics on ruby files
|
5
|
+
class RubyParser
|
6
|
+
# checks ruby (.rb)
|
7
|
+
def self.ruby(files)
|
8
|
+
files.each do |file|
|
9
|
+
# check ruby syntax
|
10
|
+
begin
|
11
|
+
catch(:good) { instance_eval("BEGIN {throw :good}; #{File.read(file)}") }
|
12
|
+
rescue ScriptError, StandardError => err
|
13
|
+
PuppetCheck.error_files.push("-- #{file}:\n#{err}")
|
14
|
+
else
|
15
|
+
# check ruby style
|
16
|
+
if PuppetCheck.style_check
|
17
|
+
require 'rubocop'
|
18
|
+
|
19
|
+
# check RuboCop and collect warnings
|
20
|
+
rubocop_warnings = Utils.capture_stdout { RuboCop::CLI.new.run(PuppetCheck.rubocop_args + ['--format', 'emacs', file]) }
|
21
|
+
warnings = rubocop_warnings == '' ? '' : rubocop_warnings.split("#{File.absolute_path(file)}:").join('')
|
22
|
+
|
23
|
+
# check Reek
|
24
|
+
if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1.0')
|
25
|
+
require 'reek'
|
26
|
+
require 'reek/cli/application'
|
27
|
+
reek_warnings = Utils.capture_stdout { Reek::CLI::Application.new([file]).execute }
|
28
|
+
warnings += reek_warnings.split("\n")[1..-1].map(&:strip).join("\n") unless reek_warnings == ''
|
29
|
+
end
|
30
|
+
|
31
|
+
# return warnings
|
32
|
+
next PuppetCheck.warning_files.push("-- #{file}:\n#{warnings.strip}") unless warnings == ''
|
33
|
+
end
|
34
|
+
PuppetCheck.clean_files.push("-- #{file}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# checks ruby template (.erb)
|
40
|
+
def self.template(files)
|
41
|
+
require 'erb'
|
42
|
+
|
43
|
+
files.each do |file|
|
44
|
+
# check ruby template syntax
|
45
|
+
begin
|
46
|
+
# need to eventually have this associated with a different binding during each iteration
|
47
|
+
# warnings = Util.capture_stderr { ERB.new(File.read(file), nil, '-').result(RubyParser.new.binding) }
|
48
|
+
warnings = Utils.capture_stderr { ERB.new(File.read(file), nil, '-').result }
|
49
|
+
# credits to gds-operations/puppet-syntax for errors to ignore
|
50
|
+
rescue NameError, TypeError
|
51
|
+
# empty out warnings since it would contain an error if this pass triggers
|
52
|
+
warnings = ''
|
53
|
+
rescue ScriptError => err
|
54
|
+
next PuppetCheck.error_files.push("-- #{file}:\n#{err}")
|
55
|
+
end
|
56
|
+
# return warnings from the check if there were any
|
57
|
+
next PuppetCheck.warning_files.push("-- #{file}:\n#{warnings.gsub('warning: ', '').split('(erb):').join('').strip}") unless warnings == ''
|
58
|
+
PuppetCheck.clean_files.push("-- #{file}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# checks librarian puppet (Puppetfile/Modulefile) and misc ruby (Rakefile/Gemfile)
|
63
|
+
def self.librarian(files)
|
64
|
+
files.each do |file|
|
65
|
+
begin
|
66
|
+
# check librarian puppet syntax
|
67
|
+
catch(:good) { instance_eval("BEGIN {throw :good}; #{File.read(file)}") }
|
68
|
+
rescue SyntaxError, LoadError, ArgumentError => err
|
69
|
+
PuppetCheck.error_files.push("-- #{file}:\n#{err}")
|
70
|
+
else
|
71
|
+
# check librarian puppet style
|
72
|
+
if PuppetCheck.style_check
|
73
|
+
require 'rubocop'
|
74
|
+
|
75
|
+
# check Rubocop
|
76
|
+
rubocop_args = PuppetCheck.rubocop_args.clone
|
77
|
+
# RuboCop is confused about the first 'mod' argument in librarian puppet (and Rakefiles and Gemfiles) so disable the Style/FileName check
|
78
|
+
rubocop_args.include?('--except') ? rubocop_args[rubocop_args.index('--except') + 1] = "#{rubocop_args[rubocop_args.index('--except') + 1]},Style/FileName" : rubocop_args.concat(['--except', 'Style/FileName'])
|
79
|
+
warnings = Utils.capture_stdout { RuboCop::CLI.new.run(rubocop_args + ['--format', 'emacs', file]) }
|
80
|
+
|
81
|
+
# collect style warnings
|
82
|
+
next PuppetCheck.warning_files.push("-- #{file}:\n#{warnings.split("#{File.absolute_path(file)}:").join('')}") unless warnings.empty?
|
83
|
+
end
|
84
|
+
PuppetCheck.clean_files.push("-- #{file}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# potentially for unique erb bindings
|
90
|
+
def binding
|
91
|
+
binding
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require_relative '../puppet-check'
|
4
|
+
|
5
|
+
# the rake interface for PuppetCheck
|
6
|
+
class PuppetCheck::Tasks < ::Rake::TaskLib
|
7
|
+
def initialize
|
8
|
+
desc 'Execute Puppet-Check file checks'
|
9
|
+
task 'puppetcheck:file' do
|
10
|
+
exit PuppetCheck.new.run(Dir.glob('*'))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
PuppetCheck::Tasks.new
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# utility methods totally not edited from StackOverflow
|
2
|
+
class Utils
|
3
|
+
# captures stdout from a block: out = capture_stdout { code }
|
4
|
+
def self.capture_stdout
|
5
|
+
old_stdout = $stdout
|
6
|
+
$stdout = StringIO.new
|
7
|
+
yield
|
8
|
+
$stdout.string
|
9
|
+
ensure
|
10
|
+
$stdout = old_stdout
|
11
|
+
end
|
12
|
+
|
13
|
+
# captures stderr from a block: err = capture_stderr { code }
|
14
|
+
def self.capture_stderr
|
15
|
+
old_stderr = $stderr
|
16
|
+
$stderr = StringIO.new
|
17
|
+
yield
|
18
|
+
$stderr.string
|
19
|
+
ensure
|
20
|
+
$stderr = old_stderr
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'puppet-check'
|
3
|
+
spec.version = '1.0.0'
|
4
|
+
spec.authors = ['Matt Schuchard']
|
5
|
+
spec.description = 'Puppet Check is a gem that provides a comprehensive, streamlined, and efficient analysis of the syntax, style, and validity of your entire Puppet code and data.'
|
6
|
+
spec.summary = 'A streamlined comprehensive set of checks for your entire Puppet code and data'
|
7
|
+
spec.homepage = 'https://www.github.com/mschuchard/puppet-check'
|
8
|
+
spec.license = 'MIT'
|
9
|
+
|
10
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
11
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |file| File.basename(file) }
|
12
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
13
|
+
spec.require_paths = Dir['lib']
|
14
|
+
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 1.9.3')
|
16
|
+
spec.add_dependency 'puppet', '>= 3.2', '< 5'
|
17
|
+
# spec.add_dependency 'rspec', '~> 3.0'
|
18
|
+
spec.add_dependency 'rake', '>= 9', '< 13'
|
19
|
+
spec.add_dependency 'rubocop', '~> 0'
|
20
|
+
spec.add_dependency 'puppet-lint', '~> 1.1'
|
21
|
+
# spec.add_dependency 'rspec-puppet', '~> 2.0'
|
22
|
+
# spec.add_dependency 'beaker.' '~> 2.0'
|
23
|
+
spec.add_dependency 'spdx-licenses', '~> 1.0'
|
24
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
foobarbaz
|
@@ -0,0 +1 @@
|
|
1
|
+
puts 'i am a good ruby file'
|