puppet-check 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|