Skip to content

Commit c35705b

Browse files
authored
(CAT-2185) Extend puppet-lint to legacy_check yaml files
* extended legacy facts check to .yaml files * add test to not detect modern facts * add test for when no search text is found when searching yaml
1 parent 91b1886 commit c35705b

File tree

5 files changed

+182
-9
lines changed

5 files changed

+182
-9
lines changed

lib/puppet-lint/bin.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ def run
6666

6767
path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
6868
path = if File.directory?(path)
69-
Dir.glob("#{path}/**/*.pp")
69+
Dir.glob([
70+
"#{path}/**/*.pp",
71+
"#{path}/**/*.{yaml,yml}",
72+
])
7073
else
7174
@args
7275
end

lib/puppet-lint/checks.rb

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ class PuppetLint::Checks
66
# Public: Get an Array of problem Hashes.
77
attr_accessor :problems
88

9+
YAML_COMPATIBLE_CHECKS = [:legacy_facts].freeze
10+
911
# Public: Initialise a new PuppetLint::Checks object.
1012
def initialize
1113
@problems = []
@@ -45,23 +47,31 @@ def load_data(path, content)
4547
end
4648
end
4749

48-
# Internal: Run the lint checks over the manifest code.
50+
# Internal: Run the lint checks over the manifest or YAML code.
4951
#
5052
# fileinfo - The path to the file as passed to puppet-lint as a String.
51-
# data - The String manifest code to be checked.
53+
# data - The String manifest or YAML code to be checked.
5254
#
5355
# Returns an Array of problem Hashes.
5456
def run(fileinfo, data)
5557
load_data(fileinfo, data)
5658

5759
checks_run = []
58-
enabled_checks.each do |check|
59-
klass = PuppetLint.configuration.check_object[check].new
60-
# FIXME: shadowing #problems
61-
problems = klass.run
62-
checks_run << [klass, problems]
60+
if File.extname(fileinfo).downcase.match?(%r{\.ya?ml$})
61+
enabled_checks.select { |check| YAML_COMPATIBLE_CHECKS.include?(check) }.each do |check|
62+
klass = PuppetLint.configuration.check_object[check].new
63+
# FIXME: shadowing #problems
64+
problems = klass.run
65+
checks_run << [klass, problems]
66+
end
67+
else
68+
enabled_checks.each do |check|
69+
klass = PuppetLint.configuration.check_object[check].new
70+
# FIXME: shadowing #problems
71+
problems = klass.run
72+
checks_run << [klass, problems]
73+
end
6374
end
64-
6575
checks_run.each do |klass, problems|
6676
if PuppetLint.configuration.fix
6777
@problems.concat(klass.fix_problems)

lib/puppet-lint/plugins/legacy_facts/legacy_facts.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'yaml'
2+
13
# Public: A puppet-lint custom check to detect legacy facts.
24
#
35
# This check will optionally convert from legacy facts like $::operatingsystem
@@ -112,6 +114,51 @@
112114

113115
PuppetLint.new_check(:legacy_facts) do
114116
def check
117+
if File.extname(PuppetLint::Data.path).downcase.match?(%r{\.ya?ml$})
118+
content = PuppetLint::Data.manifest_lines
119+
yaml_content = content.join("\n")
120+
data = YAML.safe_load(yaml_content, aliases: true, permitted_classes: [Symbol])
121+
search_yaml(data)
122+
else
123+
check_puppet
124+
end
125+
end
126+
127+
def search_yaml(data, path = [])
128+
case data
129+
when Hash
130+
data.each do |k, v|
131+
search_value(k.to_s, path)
132+
search_yaml(v, path + [k.to_s])
133+
end
134+
when Array
135+
data.each_with_index { |v, i| search_yaml(v, path + [i]) }
136+
when String
137+
search_value(data, path)
138+
end
139+
end
140+
141+
def search_value(value, _path)
142+
value.scan(%r{%{(?:(?:::?)?|facts\.)([a-zA-Z0-9_]+)(?!\.[a-zA-Z])}}) do |match|
143+
base_fact = match[0].split('.').first
144+
next unless EASY_FACTS.include?(base_fact) || UNCONVERTIBLE_FACTS.include?(base_fact) || base_fact.match(Regexp.union(REGEX_FACTS))
145+
146+
notify :warning, {
147+
message: "legacy fact '#{base_fact}'",
148+
line: find_line_for_content(value),
149+
column: 1
150+
}
151+
end
152+
end
153+
154+
def find_line_for_content(content)
155+
PuppetLint::Data.manifest_lines.each_with_index do |line, index|
156+
return index + 1 if line.include?(content)
157+
end
158+
1
159+
end
160+
161+
def check_puppet
115162
tokens.select { |x| LEGACY_FACTS_VAR_TYPES.include?(x.type) }.each do |token|
116163
fact_name = ''
117164

spec/unit/puppet-lint/checks_spec.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,60 @@
196196
end
197197
end
198198

199+
describe '#run_yaml' do
200+
let(:fileinfo) { File.join('path', 'to', 'test.yaml') }
201+
let(:data) do
202+
<<~EOS
203+
---
204+
version: 5
205+
defaults:
206+
datadir: data
207+
data_hash: yaml_data
208+
hierarchy:
209+
- name: "LEGACY FACT PATH"
210+
paths:
211+
- "os/%{facts.hostname}.yaml"
212+
- name: "osfamily/major release"
213+
paths:
214+
- "os/%{facts.os.name}/%{facts.os.release.major}.yaml"
215+
- name: 'common'
216+
path: 'common.yaml'
217+
EOS
218+
end
219+
let(:enabled_checks) { [:legacy_facts] }
220+
221+
before(:each) do
222+
allow(instance).to receive(:enabled_checks).and_return(enabled_checks) # rubocop: disable RSpec/SubjectStub
223+
allow(File).to receive(:extname).with(fileinfo).and_return('.yaml')
224+
end
225+
226+
it 'loads the yaml data' do
227+
expect(instance).to receive(:load_data).with(fileinfo, data).and_call_original # rubocop: disable RSpec/SubjectStub
228+
instance.run(fileinfo, data)
229+
end
230+
231+
context 'when there are checks enabled' do
232+
let(:enabled_checks) { [:legacy_facts] }
233+
let(:enabled_check_classes) { enabled_checks.map { |r| PuppetLint.configuration.check_object[r] } }
234+
let(:disabled_checks) { PuppetLint.configuration.checks - enabled_checks }
235+
let(:disabled_check_classes) { disabled_checks.map { |r| PuppetLint.configuration.check_object[r] } }
236+
237+
it 'runs the enabled checks' do
238+
expect(enabled_check_classes).to all(receive(:new).and_call_original)
239+
240+
instance.run(fileinfo, data)
241+
end
242+
243+
it 'does not run the disabled checks' do
244+
disabled_check_classes.each do |check_class|
245+
expect(check_class).not_to receive(:new)
246+
end
247+
248+
instance.run(fileinfo, data)
249+
end
250+
end
251+
end
252+
199253
describe '#enabled_checks' do
200254
subject(:enabled_checks) { instance.enabled_checks }
201255

spec/unit/puppet-lint/plugins/legacy_facts/legacy_facts_spec.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,65 @@
129129
expect(problems.size).to eq(1)
130130
end
131131
end
132+
133+
context 'YAML file processing' do
134+
before(:each) do
135+
allow(File).to receive(:extname).and_return('.yaml')
136+
end
137+
138+
context 'with YAML string containing legacy fact' do
139+
let(:code) { 'some_key: "%{::osfamily}"' }
140+
141+
it 'detects a single problem' do
142+
expect(problems.size).to eq(1)
143+
end
144+
end
145+
146+
context 'with YAML string not containing legacy fact' do
147+
let(:code) { 'some_key: "%{facts.os.name}"' }
148+
149+
it 'does not detect any problems' do
150+
expect(problems).to be_empty
151+
end
152+
end
153+
154+
context 'with YAML nested structure containing legacy fact' do
155+
let(:code) { "nested:\n value: \"%{::architecture}\"" }
156+
157+
it 'detects a single problem' do
158+
expect(problems.size).to eq(1)
159+
end
160+
end
161+
162+
context 'with YAML array containing legacy facts' do
163+
let(:code) do
164+
[
165+
'array:',
166+
' - "%{::processor0}"',
167+
' - "%{::ipaddress_eth0}"',
168+
].join("\n")
169+
end
170+
171+
it 'detects multiple problems' do
172+
expect(problems.size).to eq(2)
173+
end
174+
end
175+
176+
context 'with YAML alias containing legacy fact' do
177+
let(:code) do
178+
[
179+
'template: &template',
180+
' fact: "%{::osfamily}"',
181+
'instance:',
182+
' <<: *template',
183+
].join("\n")
184+
end
185+
186+
it 'detects multiple instances' do
187+
expect(problems.size).to eq(2)
188+
end
189+
end
190+
end
132191
end
133192

134193
context 'with fix enabled' do

0 commit comments

Comments
 (0)