Skip to content

Commit 97bd8da

Browse files
authored
Add SARIF support (#40)
* add SARIF support * SARIF base path uri should end with `/` * Use better sample data in SARIF template * Add help_uri and description to SARIF output * update example values in sarif template * continue-on-error: true * use STDERR.put for warnings * remove clean step* use Pathname.new * use delete_if* remove not needed strategy matrix * fix rule index * upgrade to use upload-sarif@v2 instead of upload-sarif@v1 * removed example scan.yml
1 parent 020143b commit 97bd8da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+389
-140
lines changed

lib/puppet-lint.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def print_context(message)
161161
# problems - An Array of problem Hashes as returned by
162162
# PuppetLint::Checks#run.
163163
#
164-
# Returns nothing.
164+
# Returns array of problem.
165165
def report(problems)
166166
json = []
167167
problems.each do |message|
@@ -171,7 +171,7 @@ def report(problems)
171171

172172
next unless message[:kind] == :fixed || [message[:kind], :all].include?(configuration.error_level)
173173

174-
if configuration.json
174+
if configuration.json || configuration.sarif
175175
message['context'] = get_context(message) if configuration.with_context
176176
json << message
177177
else
@@ -180,9 +180,8 @@ def report(problems)
180180
print_github_annotation(message) if configuration.github_actions
181181
end
182182
end
183-
puts JSON.pretty_generate(json) if configuration.json
184-
185183
$stderr.puts 'Try running `puppet parser validate <file>`' if problems.any? { |p| p[:check] == :syntax }
184+
json
186185
end
187186

188187
# Public: Determine if PuppetLint found any errors in the manifest.

lib/puppet-lint/bin.rb

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'pathname'
2+
require 'uri'
13
require 'puppet-lint/optparser'
24

35
# Internal: The logic of the puppet-lint bin script, contained in a class for
@@ -46,6 +48,21 @@ def run
4648

4749
begin
4850
path = @args[0]
51+
full_path = File.expand_path(path, ENV['PWD'])
52+
full_base_path = if File.directory?(full_path)
53+
full_path
54+
else
55+
File.dirname(full_path)
56+
end
57+
58+
full_base_path_uri = if full_base_path.start_with?('/')
59+
'file://' + full_base_path
60+
else
61+
'file:///' + full_base_path
62+
end
63+
64+
full_base_path_uri += '/' unless full_base_path_uri.end_with?('/')
65+
4966
path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
5067
path = if File.directory?(path)
5168
Dir.glob("#{path}/**/*.pp")
@@ -57,15 +74,14 @@ def run
5774

5875
return_val = 0
5976
ignore_paths = PuppetLint.configuration.ignore_paths
77+
all_problems = []
6078

61-
puts '[' if PuppetLint.configuration.json
6279
path.each do |f|
6380
next if ignore_paths.any? { |p| File.fnmatch(p, f) }
6481
l = PuppetLint.new
6582
l.file = f
6683
l.run
67-
l.print_problems
68-
puts ',' if f != path.last && PuppetLint.configuration.json
84+
all_problems << l.print_problems
6985

7086
if l.errors? || (l.warnings? && PuppetLint.configuration.fail_on_warnings)
7187
return_val = 1
@@ -76,7 +92,17 @@ def run
7692
fd.write(l.manifest)
7793
end
7894
end
79-
puts ']' if PuppetLint.configuration.json
95+
96+
if PuppetLint.configuration.sarif
97+
report_sarif(all_problems, full_base_path, full_base_path_uri)
98+
elsif PuppetLint.configuration.json
99+
all_problems.each do |problems|
100+
problems.each do |problem|
101+
problem.delete_if { |key, _| [:description, :help_uri].include?(key) }
102+
end
103+
end
104+
puts JSON.pretty_generate(all_problems)
105+
end
80106

81107
return return_val
82108
rescue PuppetLint::NoCodeError
@@ -85,4 +111,42 @@ def run
85111
return 1
86112
end
87113
end
114+
115+
# Internal: Print the reported problems in SARIF format to stdout.
116+
#
117+
# problems - An Array of problem Hashes as returned by
118+
# PuppetLint::Checks#run.
119+
#
120+
# Returns nothing.
121+
def report_sarif(problems, base_path, base_path_uri)
122+
sarif_file = File.read(File.join(__dir__, 'report', 'sarif_template.json'))
123+
sarif = JSON.parse(sarif_file)
124+
sarif['runs'][0]['originalUriBaseIds']['ROOTPATH']['uri'] = base_path_uri
125+
rules = sarif['runs'][0]['tool']['driver']['rules'] = []
126+
results = sarif['runs'][0]['results'] = []
127+
problems.each do |messages|
128+
messages.each do |message|
129+
relative_path = Pathname.new(message[:fullpath]).relative_path_from(Pathname.new(base_path))
130+
rules = sarif['runs'][0]['tool']['driver']['rules']
131+
rule_exists = rules.any? { |r| r['id'] == message[:check] }
132+
unless rule_exists
133+
new_rule = { 'id' => message[:check] }
134+
new_rule['fullDescription'] = { 'text' => message[:description] } unless message[:description].nil?
135+
new_rule['fullDescription'] = { 'text' => 'Check for any syntax error and record an error of each instance found.' } if new_rule['fullDescription'].nil? && message[:check] == :syntax
136+
new_rule['defaultConfiguration'] = { 'level' => message[:KIND].downcase } if %w[error warning].include?(message[:KIND].downcase)
137+
new_rule['helpUri'] = message[:help_uri] unless message[:help_uri].nil?
138+
rules << new_rule
139+
end
140+
rule_index = rules.index { |r| r['id'] == message[:check] }
141+
result = {
142+
'ruleId' => message[:check],
143+
'ruleIndex' => rule_index,
144+
'message' => { 'text' => message[:message] },
145+
'locations' => [{ 'physicalLocation' => { 'artifactLocation' => { 'uri' => relative_path, 'uriBaseId' => 'ROOTPATH' }, 'region' => { 'startLine' => message[:line], 'startColumn' => message[:column] } } }],
146+
}
147+
results << result
148+
end
149+
end
150+
puts JSON.pretty_generate(sarif)
151+
end
88152
end

lib/puppet-lint/checkplugin.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,12 @@ def default_info
158158
#
159159
# kind - The Symbol problem type (:warning or :error).
160160
# problem - A Hash containing the attributes of the problem
161-
# :message - The String message describing the problem.
162-
# :line - The Integer line number of the location of the problem.
163-
# :column - The Integer column number of the location of the problem.
164-
# :check - The Symbol name of the check that detected the problem.
161+
# :message - The String message describing the problem.
162+
# :line - The Integer line number of the location of the problem.
163+
# :column - The Integer column number of the location of the problem.
164+
# :check - The Symbol name of the check that detected the problem.
165+
# :description - The description of the check that detected the problem.
166+
# :help_uri - The help web page of the check that detected the problem.
165167
#
166168
# Returns nothing.
167169
def notify(kind, problem)

lib/puppet-lint/configuration.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def defaults
150150
self.with_context = false
151151
self.fix = false
152152
self.json = false
153+
self.sarif = false
153154
self.show_ignored = false
154155
self.ignore_paths = ['vendor/**/*.pp']
155156
self.github_actions = ENV.key?('GITHUB_ACTION')

lib/puppet-lint/data.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ def parse_control_comments
591591
if top_override.nil?
592592
# TODO: refactor to provide a way to expose problems from
593593
# PuppetLint::Data via the normal problem reporting mechanism.
594-
puts "WARNING: lint:endignore comment with no opening lint:ignore:<check> comment found on line #{token.line}"
594+
$stderr.puts "WARNING: lint:endignore comment with no opening lint:ignore:<check> comment found on line #{token.line}"
595595
else
596596
top_override.each do |start|
597597
next if start.nil?
@@ -607,7 +607,7 @@ def parse_control_comments
607607
end
608608

609609
stack.each do |control|
610-
puts "WARNING: lint:ignore:#{control[0][2]} comment on line #{control[0][0]} with no closing lint:endignore comment"
610+
$stderr.puts "WARNING: lint:ignore:#{control[0][2]} comment on line #{control[0][0]} with no closing lint:endignore comment"
611611
end
612612
end
613613
end

lib/puppet-lint/optparser.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ def self.build(args = [])
9898
PuppetLint.configuration.json = true
9999
end
100100

101+
opts.on('--sarif', 'Log output as SARIF') do
102+
PuppetLint.configuration.sarif = true
103+
end
104+
101105
opts.on('--list-checks', 'List available check names.') do
102106
PuppetLint.configuration.list_checks = true
103107
end

lib/puppet-lint/plugins/check_classes/arrow_on_right_operand_line.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ def check
99

1010
notify(
1111
:warning,
12-
:message => "arrow should be on the right operand's line",
13-
:line => token.line,
14-
:column => token.column,
15-
:token => token
12+
:message => "arrow should be on the right operand's line",
13+
:line => token.line,
14+
:column => token.column,
15+
:token => token,
16+
:description => 'Test the manifest tokens for chaining arrow that is on the line of the left operand when the right operand is on another line.',
17+
:help_uri => 'https://puppet.com/docs/puppet/latest/style_guide.html#chaining-arrow-syntax'
1618
)
1719
end
1820
end

lib/puppet-lint/plugins/check_classes/autoloader_layout.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ def check
2525

2626
notify(
2727
:error,
28-
:message => "#{title_token.value} not in autoload module layout",
29-
:line => title_token.line,
30-
:column => title_token.column
28+
:message => "#{title_token.value} not in autoload module layout",
29+
:line => title_token.line,
30+
:column => title_token.column,
31+
:description => 'Test the manifest tokens for any classes or defined types that are not in an appropriately named file for the autoloader to detect and record an error of each instance found.',
32+
:help_uri => 'https://puppet.com/docs/puppet/latest/style_guide.html#separate-files'
3133
)
3234
end
3335
end

lib/puppet-lint/plugins/check_classes/class_inherits_from_params_class.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ def check
99

1010
notify(
1111
:warning,
12-
:message => 'class inheriting from params class',
13-
:line => class_idx[:inherited_token].line,
14-
:column => class_idx[:inherited_token].column
12+
:message => 'class inheriting from params class',
13+
:line => class_idx[:inherited_token].line,
14+
:column => class_idx[:inherited_token].column,
15+
:description => 'Check the manifest tokens for any classes that inherit a params subclass and record a warning for each instance found.',
16+
:help_uri => nil
1517
)
1618
end
1719
end

lib/puppet-lint/plugins/check_classes/code_on_top_scope.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ def check
1111

1212
notify(
1313
:warning,
14-
:message => "code outside of class or define block - #{token.value}",
15-
:line => token.line,
16-
:column => token.column
14+
:message => "code outside of class or define block - #{token.value}",
15+
:line => token.line,
16+
:column => token.column,
17+
:description => 'Test that no code is outside of a class or define scope.',
18+
:help_uri => nil
1719
)
1820
end
1921
end

0 commit comments

Comments
 (0)