puppetlabs-syntax 7.2.1
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/.github/workflows/ci.yml +38 -0
- data/.github/workflows/mend.yml +15 -0
- data/.github/workflows/nightly.yml +30 -0
- data/.github/workflows/release.yml +16 -0
- data/.github/workflows/release_prep.yml +20 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +177 -0
- data/CHANGELOG.md +183 -0
- data/CODEOWNERS +2 -0
- data/Gemfile +59 -0
- data/HISTORY.md +382 -0
- data/LICENSE.txt +22 -0
- data/README.md +198 -0
- data/Rakefile +26 -0
- data/lib/puppetlabs-syntax/hiera.rb +176 -0
- data/lib/puppetlabs-syntax/manifests.rb +70 -0
- data/lib/puppetlabs-syntax/tasks/puppetlabs-syntax.rb +86 -0
- data/lib/puppetlabs-syntax/templates.rb +90 -0
- data/lib/puppetlabs-syntax/version.rb +5 -0
- data/lib/puppetlabs-syntax.rb +42 -0
- data/puppetlabs-syntax.gemspec +28 -0
- data/spec/fixtures/hiera/data/hiera_1.yaml +0 -0
- data/spec/fixtures/hiera/data/hiera_2.eyaml +0 -0
- data/spec/fixtures/hiera/data/test/hiera_3.yaml +0 -0
- data/spec/fixtures/hiera/data/test/hiera_4.eyaml +0 -0
- data/spec/fixtures/hiera/hiera_bad.eyaml +25 -0
- data/spec/fixtures/hiera/hiera_bad.yaml +1 -0
- data/spec/fixtures/hiera/hiera_badkey.yaml +18 -0
- data/spec/fixtures/hiera/hiera_badvalue.yaml +8 -0
- data/spec/fixtures/hiera/hiera_good.eyaml +13 -0
- data/spec/fixtures/hiera/hiera_good.yaml +7 -0
- data/spec/fixtures/hiera/hiera_key_empty.yaml +0 -0
- data/spec/fixtures/hiera/hiera_key_no_value.yaml +2 -0
- data/spec/fixtures/test_module/manifests/deprecation_notice.pp +6 -0
- data/spec/fixtures/test_module/manifests/fail_error.pp +3 -0
- data/spec/fixtures/test_module/manifests/fail_warning.pp +5 -0
- data/spec/fixtures/test_module/manifests/future_syntax.pp +3 -0
- data/spec/fixtures/test_module/manifests/pass.pp +5 -0
- data/spec/fixtures/test_module/manifests/pass_storeconfigs.pp +3 -0
- data/spec/fixtures/test_module/manifests/schedule_notice.pp +5 -0
- data/spec/fixtures/test_module/manifests/tag_notice.pp +5 -0
- data/spec/fixtures/test_module/manifests/test_app.pp +3 -0
- data/spec/fixtures/test_module/templates/fail_error.epp +3 -0
- data/spec/fixtures/test_module/templates/fail_error.erb +3 -0
- data/spec/fixtures/test_module/templates/fail_error_also.epp +3 -0
- data/spec/fixtures/test_module/templates/fail_warning.erb +2 -0
- data/spec/fixtures/test_module/templates/ignore.tpl +3 -0
- data/spec/fixtures/test_module/templates/pass.epp +3 -0
- data/spec/fixtures/test_module/templates/pass.erb +2 -0
- data/spec/fixtures/test_module/templates/pass_also.epp +3 -0
- data/spec/fixtures/test_module/templates/pass_unbound_var.erb +1 -0
- data/spec/fixtures/test_module/templates/typeerror_shouldwin.erb +2 -0
- data/spec/puppetlabs-syntax/hiera_spec.rb +107 -0
- data/spec/puppetlabs-syntax/manifests_spec.rb +96 -0
- data/spec/puppetlabs-syntax/tasks/puppetlabs-syntax_spec.rb +55 -0
- data/spec/puppetlabs-syntax/templates_spec.rb +127 -0
- data/spec/puppetlabs-syntax_spec.rb +42 -0
- data/spec/spec_helper.rb +26 -0
- metadata +150 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'base64'
|
|
5
|
+
|
|
6
|
+
module PuppetlabsSyntax
|
|
7
|
+
class Hiera
|
|
8
|
+
def check_hiera_key(key)
|
|
9
|
+
if key.is_a? Symbol
|
|
10
|
+
if key.to_s.start_with?(':')
|
|
11
|
+
"Puppet automatic lookup will not use leading '::'"
|
|
12
|
+
elsif !/^[a-z]+$/.match?(key) # we allow Hiera's own configuration
|
|
13
|
+
'Puppet automatic lookup will not look up symbols'
|
|
14
|
+
end
|
|
15
|
+
elsif !/^([a-z][a-z0-9_]+::)*[a-z0-9_][a-zA-Z0-9_]+$/.match?(key) # adapted from Puppet docs combining namespaced and un-namespaced regex
|
|
16
|
+
return 'Looks like a missing colon' if /[^:]:[^:]/.match?(key)
|
|
17
|
+
|
|
18
|
+
# be extra helpful
|
|
19
|
+
|
|
20
|
+
'Not a valid Puppet variable name for automatic lookup'
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def check_hiera_data(_key, value)
|
|
26
|
+
# using filter_map to remove nil values
|
|
27
|
+
# there will be nil values if check_broken_function_call didn't return a string
|
|
28
|
+
# this is a shorthand for filter.compact
|
|
29
|
+
# https://blog.saeloun.com/2019/05/25/ruby-2-7-enumerable-filter-map/
|
|
30
|
+
keys_and_values(value).filter_map do |element|
|
|
31
|
+
check_broken_function_call(element)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Recurse through complex data structures. Return on first error.
|
|
36
|
+
def check_eyaml_data(name, val)
|
|
37
|
+
error = nil
|
|
38
|
+
if val.is_a? String
|
|
39
|
+
err = check_eyaml_blob(val)
|
|
40
|
+
error = "Key #{name} #{err}" if err
|
|
41
|
+
elsif val.is_a? Array
|
|
42
|
+
val.each_with_index do |v, idx|
|
|
43
|
+
error = check_eyaml_data("#{name}[#{idx}]", v)
|
|
44
|
+
break if error
|
|
45
|
+
end
|
|
46
|
+
elsif val.is_a? Hash
|
|
47
|
+
val.each do |k, v|
|
|
48
|
+
error = check_eyaml_data("#{name}['#{k}']", v)
|
|
49
|
+
break if error
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
error
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def check_eyaml_blob(val)
|
|
56
|
+
# strip newlines and extra spaces
|
|
57
|
+
val.gsub!(/\s+/, '')
|
|
58
|
+
|
|
59
|
+
encodes_length = val.scan('ENC[').length
|
|
60
|
+
|
|
61
|
+
# Return if there's no encoded material
|
|
62
|
+
return if encodes_length == 0
|
|
63
|
+
|
|
64
|
+
found_encodes = val.scan(/ENC\[([^,\]]+,)?([^\]]+)?\]/)
|
|
65
|
+
|
|
66
|
+
return 'has unterminated eyaml value' unless found_encodes.length == encodes_length
|
|
67
|
+
|
|
68
|
+
known_methods = %w[PKCS7 GPG GKMS KMS TWOFAC SecretBox VAULT GCPKMS RSA SSHAGENT VAULT_RS cli]
|
|
69
|
+
|
|
70
|
+
found_encodes.each do |match|
|
|
71
|
+
# if no method is found we use the default PKCS7 method
|
|
72
|
+
method = match[0]
|
|
73
|
+
method = +'PKCS7' if method.nil?
|
|
74
|
+
method.delete_suffix!(',')
|
|
75
|
+
base64 = match[1]
|
|
76
|
+
|
|
77
|
+
return 'has invalid eyaml encoded format' if base64.nil?
|
|
78
|
+
|
|
79
|
+
return "has unknown eyaml method #{method}" unless known_methods.include? method
|
|
80
|
+
|
|
81
|
+
return 'has unpadded or truncated base64 data' unless base64.length % 4 == 0
|
|
82
|
+
|
|
83
|
+
return 'has corrupt base64 data' unless base64.match?(%r{^[a-zA-Z0-9+/=]+$})
|
|
84
|
+
end
|
|
85
|
+
# all good
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def check(filelist)
|
|
90
|
+
raise 'Expected an array of files' unless filelist.is_a?(Array)
|
|
91
|
+
|
|
92
|
+
errors = []
|
|
93
|
+
|
|
94
|
+
yamlargs = (Psych::VERSION >= '4.0') ? { aliases: true } : {}
|
|
95
|
+
|
|
96
|
+
filelist.each do |hiera_file|
|
|
97
|
+
begin
|
|
98
|
+
yamldata = YAML.load_file(hiera_file, **yamlargs)
|
|
99
|
+
rescue Exception => e
|
|
100
|
+
errors << "ERROR: Failed to parse #{hiera_file}: #{e}"
|
|
101
|
+
next
|
|
102
|
+
end
|
|
103
|
+
next unless yamldata
|
|
104
|
+
|
|
105
|
+
unless yamldata.is_a?(Hash)
|
|
106
|
+
errors << "ERROR: #{hiera_file} doesn't contain a valid Hash, datatype is #{yamldata.class}"
|
|
107
|
+
next
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
yamldata.each do |k, v|
|
|
111
|
+
if PuppetlabsSyntax.check_hiera_keys
|
|
112
|
+
key_msg = check_hiera_key(k)
|
|
113
|
+
errors << "WARNING: #{hiera_file}: Key :#{k}: #{key_msg}" if key_msg
|
|
114
|
+
end
|
|
115
|
+
if PuppetlabsSyntax.check_hiera_data
|
|
116
|
+
check_hiera_data(k, v).each do |value_msg|
|
|
117
|
+
errors << "WARNING: #{hiera_file}: Key :#{k}: #{value_msg}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
eyaml_msg = check_eyaml_data(k, v)
|
|
121
|
+
errors << "WARNING: #{hiera_file}: #{eyaml_msg}" if eyaml_msg
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
errors.map! { |e| e.to_s }
|
|
126
|
+
|
|
127
|
+
errors
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# you can call functions in hiera, like this:
|
|
133
|
+
# "%{lookup('this_is_ok')}"
|
|
134
|
+
# you can do this in everywhere in a hiera value
|
|
135
|
+
# you cannot do string concatenation within {}:
|
|
136
|
+
# "%{lookup('this_is_ok'):3306}"
|
|
137
|
+
# You can do string concatenation outside of {}:
|
|
138
|
+
# "%{lookup('this_is_ok')}:3306"
|
|
139
|
+
def check_broken_function_call(element)
|
|
140
|
+
'string after a function call but before `}` in the value' if element.is_a?(String) && /%{[^}]+\('[^}]*'\)[^}\s]+}/.match?(element)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# gets a hash or array, returns all keys + values as array
|
|
144
|
+
def flatten_data(data, parent_key = [])
|
|
145
|
+
if data.is_a?(Hash)
|
|
146
|
+
data.flat_map do |key, value|
|
|
147
|
+
current_key = parent_key + [key.to_s]
|
|
148
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
|
149
|
+
flatten_data(value, current_key)
|
|
150
|
+
else
|
|
151
|
+
[current_key.join('.'), value]
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
elsif data.is_a?(Array) && !data.empty?
|
|
155
|
+
data.flat_map do |value|
|
|
156
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
|
157
|
+
flatten_data(value, parent_key)
|
|
158
|
+
else
|
|
159
|
+
[parent_key.join('.'), value]
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
[parent_key.join('.')]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# gets a string, hash or array, returns all keys + values as flattened + unique array
|
|
168
|
+
def keys_and_values(value)
|
|
169
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
|
170
|
+
flatten_data(value).flatten.uniq
|
|
171
|
+
else
|
|
172
|
+
[value]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetlabsSyntax
|
|
4
|
+
class Manifests
|
|
5
|
+
def check(filelist)
|
|
6
|
+
raise 'Expected an array of files' unless filelist.is_a?(Array)
|
|
7
|
+
|
|
8
|
+
require 'puppet'
|
|
9
|
+
require 'puppet/version'
|
|
10
|
+
require 'puppet/face'
|
|
11
|
+
require 'puppet/test/test_helper'
|
|
12
|
+
|
|
13
|
+
output = []
|
|
14
|
+
|
|
15
|
+
Puppet::Test::TestHelper.initialize
|
|
16
|
+
Puppet::Test::TestHelper.before_all_tests
|
|
17
|
+
called_before_all_tests = true
|
|
18
|
+
|
|
19
|
+
# Catch syntax warnings.
|
|
20
|
+
Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(output))
|
|
21
|
+
Puppet::Util::Log.level = :warning
|
|
22
|
+
|
|
23
|
+
filelist.each do |puppet_file|
|
|
24
|
+
Puppet::Test::TestHelper.before_each_test
|
|
25
|
+
begin
|
|
26
|
+
error = validate_manifest(puppet_file)
|
|
27
|
+
output << error.values.first if error.is_a?(Hash) && !error.empty? # Puppet 6.5.0 onwards
|
|
28
|
+
rescue SystemExit
|
|
29
|
+
# Disregard exit(1) from face.
|
|
30
|
+
# This is how puppet < 6.5.0 `validate_manifest` worked.
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
output << e
|
|
33
|
+
ensure
|
|
34
|
+
Puppet::Test::TestHelper.after_each_test
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Puppet::Util::Log.close_all
|
|
39
|
+
output.map! { |e| e.to_s }
|
|
40
|
+
|
|
41
|
+
# Exported resources will raise warnings when outside a puppetmaster.
|
|
42
|
+
output.reject! do |e|
|
|
43
|
+
e =~ /^You cannot collect( exported resources)? without storeconfigs being set/
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# tag and schedule parameters in class raise warnings notice in output that prevent from succeed
|
|
47
|
+
output.reject! do |e|
|
|
48
|
+
e =~ /^(tag|schedule) is a metaparam; this value will inherit to all contained resources in the /
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
deprecations = output.select do |e|
|
|
52
|
+
e =~ /^Deprecation notice:|is deprecated/
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Errors exist if there is any output that isn't a deprecation notice.
|
|
56
|
+
has_errors = (output != deprecations)
|
|
57
|
+
|
|
58
|
+
[output, has_errors]
|
|
59
|
+
ensure
|
|
60
|
+
Puppet::Test::TestHelper.after_all_tests if called_before_all_tests
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def validate_manifest(file)
|
|
66
|
+
Puppet[:tasks] = true if %r{.*plans/.*\.pp$}.match?(file)
|
|
67
|
+
Puppet::Face[:parser, :current].validate(file)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppetlabs-syntax'
|
|
4
|
+
require 'rake'
|
|
5
|
+
require 'rake/tasklib'
|
|
6
|
+
|
|
7
|
+
module PuppetlabsSyntax
|
|
8
|
+
class RakeTask < ::Rake::TaskLib
|
|
9
|
+
def filelist(paths)
|
|
10
|
+
excludes = PuppetlabsSyntax.exclude_paths
|
|
11
|
+
excludes.push('pkg/**/*')
|
|
12
|
+
excludes.push('vendor/**/*')
|
|
13
|
+
files = FileList[paths]
|
|
14
|
+
files.reject! { |f| File.directory?(f) }
|
|
15
|
+
files.exclude(*excludes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def filelist_manifests
|
|
19
|
+
filelist(PuppetlabsSyntax.manifests_paths)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def filelist_templates
|
|
23
|
+
filelist(PuppetlabsSyntax.templates_paths)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def filelist_hiera_yaml
|
|
27
|
+
filelist(PuppetlabsSyntax.hieradata_paths)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(*_args)
|
|
31
|
+
desc 'Syntax check for Puppet manifests, templates and Hiera'
|
|
32
|
+
task syntax: [
|
|
33
|
+
'syntax:manifests',
|
|
34
|
+
'syntax:templates',
|
|
35
|
+
'syntax:hiera',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
namespace :syntax do
|
|
39
|
+
desc 'Syntax check Puppet manifests'
|
|
40
|
+
task :manifests do |t|
|
|
41
|
+
warn "---> #{t.name}"
|
|
42
|
+
|
|
43
|
+
c = PuppetlabsSyntax::Manifests.new
|
|
44
|
+
output, has_errors = c.check(filelist_manifests)
|
|
45
|
+
$stdout.puts "#{output.join("\n")}\n" unless output.empty?
|
|
46
|
+
exit 1 if has_errors || (output.any? && PuppetlabsSyntax.fail_on_deprecation_notices)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc 'Syntax check Puppet templates'
|
|
50
|
+
task :templates do |t|
|
|
51
|
+
warn "---> #{t.name}"
|
|
52
|
+
|
|
53
|
+
c = PuppetlabsSyntax::Templates.new
|
|
54
|
+
result = c.check(filelist_templates)
|
|
55
|
+
unless result[:warnings].empty?
|
|
56
|
+
$stdout.puts 'WARNINGS:'
|
|
57
|
+
$stdout.puts result[:warnings].join("\n")
|
|
58
|
+
end
|
|
59
|
+
unless result[:errors].empty?
|
|
60
|
+
warn 'ERRORS:'
|
|
61
|
+
warn result[:errors].join("\n")
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
desc 'Syntax check Hiera config files'
|
|
67
|
+
task hiera: [
|
|
68
|
+
'syntax:hiera:yaml',
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
namespace :hiera do
|
|
72
|
+
task :yaml do |t|
|
|
73
|
+
warn "---> #{t.name}"
|
|
74
|
+
warn "#{t.name} was called, but PuppetlabsSyntax.check_hiera_keys is false. hiera syntax won't be checked" unless PuppetlabsSyntax.check_hiera_keys
|
|
75
|
+
c = PuppetlabsSyntax::Hiera.new
|
|
76
|
+
errors = c.check(filelist_hiera_yaml)
|
|
77
|
+
$stdout.puts "#{errors.join("\n")}\n" unless errors.empty?
|
|
78
|
+
exit 1 unless errors.empty?
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
PuppetlabsSyntax::RakeTask.new
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
module PuppetlabsSyntax
|
|
7
|
+
class Templates
|
|
8
|
+
def check(filelist)
|
|
9
|
+
raise 'Expected an array of files' unless filelist.is_a?(Array)
|
|
10
|
+
|
|
11
|
+
# We now have to redirect STDERR in order to capture warnings.
|
|
12
|
+
$stderr = warnings = StringIO.new
|
|
13
|
+
result = { warnings: [], errors: [] }
|
|
14
|
+
|
|
15
|
+
filelist.each do |file|
|
|
16
|
+
if File.extname(file) == '.epp' or PuppetlabsSyntax.epp_only
|
|
17
|
+
tmp = validate_epp(file)
|
|
18
|
+
elsif File.extname(file) == '.erb'
|
|
19
|
+
tmp = validate_erb(file)
|
|
20
|
+
end
|
|
21
|
+
result.merge!(tmp) { |_k, a, b| a.concat(b) } unless tmp.nil?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
$stderr = STDERR
|
|
25
|
+
result[:warnings] << warnings.string unless warnings.string.empty?
|
|
26
|
+
|
|
27
|
+
result[:errors].map! { |e| e.to_s }
|
|
28
|
+
result[:warnings].map! { |w| w.to_s }
|
|
29
|
+
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def validate_epp(filename)
|
|
34
|
+
require 'puppet'
|
|
35
|
+
result = { warnings: [], errors: [] }
|
|
36
|
+
formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new
|
|
37
|
+
evaluating_parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new
|
|
38
|
+
parser = evaluating_parser.parser
|
|
39
|
+
begin
|
|
40
|
+
parse_result = parser.parse_file(filename)
|
|
41
|
+
validation_result = evaluating_parser.validate(parse_result.model)
|
|
42
|
+
|
|
43
|
+
# print out any warnings
|
|
44
|
+
validation_result.warnings.each do |warn|
|
|
45
|
+
message = formatter.format_message(warn)
|
|
46
|
+
file = warn.file
|
|
47
|
+
line = warn.source_pos.line
|
|
48
|
+
column = warn.source_pos.pos
|
|
49
|
+
result[:warnings] << "#{file}:#{line}:#{column}: #{message}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# collect errors and return them in order to indicate validation failure
|
|
53
|
+
validation_result.errors.each do |err|
|
|
54
|
+
message = formatter.format_message(err)
|
|
55
|
+
file = err.file
|
|
56
|
+
line = err.source_pos.line
|
|
57
|
+
column = err.source_pos.pos
|
|
58
|
+
result[:errors] << "#{file}:#{line}:#{column}: #{message}"
|
|
59
|
+
end
|
|
60
|
+
rescue Puppet::ParseError, SyntaxError => e
|
|
61
|
+
result[:errors] << e
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
result[:errors] << e
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate_erb(filename)
|
|
70
|
+
result = { warnings: [], errors: [] }
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
template = File.read(filename)
|
|
74
|
+
erb = ERB.new(template, trim_mode: '-')
|
|
75
|
+
erb.filename = filename
|
|
76
|
+
erb.result
|
|
77
|
+
rescue NameError
|
|
78
|
+
# This is normal because we don't have the variables that would
|
|
79
|
+
# ordinarily be bound by the parent Puppet manifest.
|
|
80
|
+
rescue TypeError
|
|
81
|
+
# This is normal because we don't have the variables that would
|
|
82
|
+
# ordinarily be bound by the parent Puppet manifest.
|
|
83
|
+
rescue SyntaxError => e
|
|
84
|
+
result[:errors] << e
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
result
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppetlabs-syntax/version'
|
|
4
|
+
|
|
5
|
+
module PuppetlabsSyntax
|
|
6
|
+
autoload :Hiera, 'puppetlabs-syntax/hiera'
|
|
7
|
+
autoload :Manifests, 'puppetlabs-syntax/manifests'
|
|
8
|
+
autoload :Templates, 'puppetlabs-syntax/templates'
|
|
9
|
+
|
|
10
|
+
@exclude_paths = [
|
|
11
|
+
'spec/fixtures/**/*',
|
|
12
|
+
'pkg/**/*',
|
|
13
|
+
'vendor/**/*',
|
|
14
|
+
'.vendor/**/*',
|
|
15
|
+
]
|
|
16
|
+
@hieradata_paths = [
|
|
17
|
+
'**/data/**/*.*{yaml,yml}',
|
|
18
|
+
'hieradata/**/*.*{yaml,yml}',
|
|
19
|
+
'hiera*.*{yaml,yml}',
|
|
20
|
+
]
|
|
21
|
+
@manifests_paths = [
|
|
22
|
+
'**/*.pp',
|
|
23
|
+
]
|
|
24
|
+
@templates_paths = [
|
|
25
|
+
'**/templates/**/*.erb',
|
|
26
|
+
'**/templates/**/*.epp',
|
|
27
|
+
]
|
|
28
|
+
@fail_on_deprecation_notices = true
|
|
29
|
+
@check_hiera_keys = true
|
|
30
|
+
@check_hiera_data = true
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
attr_accessor :exclude_paths,
|
|
34
|
+
:hieradata_paths,
|
|
35
|
+
:manifests_paths,
|
|
36
|
+
:templates_paths,
|
|
37
|
+
:fail_on_deprecation_notices,
|
|
38
|
+
:epp_only,
|
|
39
|
+
:check_hiera_keys,
|
|
40
|
+
:check_hiera_data
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'puppetlabs-syntax/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'puppetlabs-syntax'
|
|
9
|
+
spec.version = PuppetlabsSyntax::VERSION
|
|
10
|
+
spec.email = ['modules-team@puppet.com']
|
|
11
|
+
spec.authors = ['Puppet, Inc.']
|
|
12
|
+
spec.summary = 'Syntax checks for Puppet manifests, templates, and Hiera YAML'
|
|
13
|
+
spec.homepage = 'https://github.com/puppetlabs/puppetlabs-syntax/'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.description = <<-DESC
|
|
16
|
+
Syntax checks for Puppet manifests and templates.
|
|
17
|
+
DESC
|
|
18
|
+
spec.files = `git ls-files`.split($/)
|
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
20
|
+
spec.require_paths = ['lib']
|
|
21
|
+
|
|
22
|
+
spec.required_ruby_version = '>= 3.2'
|
|
23
|
+
|
|
24
|
+
spec.add_dependency 'puppet', '>= 8', '< 10'
|
|
25
|
+
spec.add_dependency 'rake', '~> 13.1'
|
|
26
|
+
|
|
27
|
+
spec.add_development_dependency 'voxpupuli-rubocop', '~> 5.2.0'
|
|
28
|
+
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
acme::warning1: ENC[unknown-method,aGVsbG8sIHdvcmxk] # unknown method
|
|
3
|
+
acme::warning2: ENC[PKCS7,aGVsbG8sIHdvcmxk # has unterminated eyaml value
|
|
4
|
+
acme::warning3: ENC[PKCS7,aGVsbG8sIHdvcmxk==] # unpadded or truncated base64 data
|
|
5
|
+
acme::warning4: ENC[PKCS7,aGVsbG8sf&IHdvcmxk==] # corrupt base64 data
|
|
6
|
+
acme::warning5:
|
|
7
|
+
key1: foo
|
|
8
|
+
key2: ENC[PKCS7,aGVs!!!!bG8sIHdvcmxk] # corrupt base64 data
|
|
9
|
+
acme::warning6:
|
|
10
|
+
hash_key:
|
|
11
|
+
- element1
|
|
12
|
+
- >
|
|
13
|
+
ENC[PKCS7,
|
|
14
|
+
aGVsbG8sIHdvcmxk
|
|
15
|
+
]
|
|
16
|
+
- >
|
|
17
|
+
ENC[PKCS7,
|
|
18
|
+
aGVs!!!!bG8sIHdvcmxk
|
|
19
|
+
]
|
|
20
|
+
# corrupt base64 data
|
|
21
|
+
acme::warning7: ENC[] # has invalid eyaml encoded format
|
|
22
|
+
acme::warning8: ENC[PKCS7,aGV&&&sbG8sIHdvcmxk] # unpadded or truncated base64 data
|
|
23
|
+
acme::warning9: | # has unterminated eyaml value
|
|
24
|
+
ENC[aGVsbG8sIHdvcmxk
|
|
25
|
+
ENC[aGVsbG8sIHdvcmxk]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
%YAMLBAD
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
this_is_ok: 0
|
|
3
|
+
this_is_ok::too: 0
|
|
4
|
+
th1s_is_ok::two3: 0
|
|
5
|
+
:eventhis: 0
|
|
6
|
+
good::value_with_CAPS: true
|
|
7
|
+
rootValueWithCaps3: true
|
|
8
|
+
|
|
9
|
+
typical:typo::warning1: true
|
|
10
|
+
::notsotypical::warning2: true
|
|
11
|
+
noCamelCase::warning3: true
|
|
12
|
+
root::noCamelCase::warning4: true
|
|
13
|
+
Leading_caps_warning5: true
|
|
14
|
+
namespace::Leading_warning6: true
|
|
15
|
+
no-hyphens::warning7: true
|
|
16
|
+
:picky::warning8: true
|
|
17
|
+
this_is::warning9:
|
|
18
|
+
"%{lookup('foobar'):3306}": []
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
acme::good1: ENC[KMS,aGVsbG8sIdGHdvcmxk==]
|
|
3
|
+
acme::good2: ENC[PKCS7,aGVsbG8sf3IHdvcmxk==]
|
|
4
|
+
acme::good3: ENC[KMS,aGVsbG8sIdGHdvcmxk==]ENC[KMS,aGVsbG8sIdGHdvcmxk==]
|
|
5
|
+
acme::good4: >
|
|
6
|
+
ENC[PKCS7,
|
|
7
|
+
aGVsbG8sIHdvcmxk]
|
|
8
|
+
acme::good5: ENC[GPG,aGVsbG8sIHdvcmxkIQ==]
|
|
9
|
+
acme::good6: ENC[GPG,aGVsbG8sIHdvcmxkISE=]
|
|
10
|
+
acme::good7: |
|
|
11
|
+
text
|
|
12
|
+
ENC[GPG,aGVsbG8sIHdvcmxkISE=]
|
|
13
|
+
acme::good8: ENC[aGVsbG8sf3IHdvcmxk==]
|
|
File without changes
|