Skip to content

Commit f272197

Browse files
committed
Add support for generating codeclimate report
1 parent bbcb5c2 commit f272197

File tree

10 files changed

+179
-18
lines changed

10 files changed

+179
-18
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,30 @@ There is a GitHub Actions action available to get linter feedback in workflows:
215215

216216
* [puppet-lint-action](https://github.com/marketplace/actions/puppet-lint-action)
217217

218+
## Integration with GitLab Code Quality
219+
220+
[GitLab](https://gitlab.com/) users can use the `--codeclimate-report-file` configuration option to generate a report for use with the
221+
[Code Quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) feature.
222+
223+
The easiest way to set this option, (and without having to modify rake tasks), is with the `CODECLIMATE_REPORT_FILE` environment variable.
224+
225+
For example, the following GitLab job sets the environment variable and
226+
[archives the report](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality) produced.
227+
```yaml
228+
validate lint check rubocop-Ruby 2.7.2-Puppet ~> 7:
229+
stage: syntax
230+
image: ruby:2.7.2
231+
script:
232+
- bundle exec rake validate lint check rubocop
233+
variables:
234+
PUPPET_GEM_VERSION: '~> 7'
235+
CODECLIMATE_REPORT_FILE: 'gl-code-quality-report.json'
236+
artifacts:
237+
reports:
238+
codequality: gl-code-quality-report.json
239+
expire_in: 1 week
240+
```
241+
218242
## Options
219243
220244
See `puppet-lint --help` for a full list of command line options and checks.

lib/puppet-lint.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,16 @@ def report(problems)
175175

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

178-
if configuration.json || configuration.sarif
178+
if configuration.json || configuration.sarif || configuration.codeclimate_report_file
179179
message['context'] = get_context(message) if configuration.with_context
180180
json << message
181-
else
182-
format_message(message)
183-
print_context(message) if configuration.with_context
184-
print_github_annotation(message) if configuration.github_actions
185181
end
182+
183+
next if configuration.json || configuration.sarif
184+
185+
format_message(message)
186+
print_context(message) if configuration.with_context
187+
print_github_annotation(message) if configuration.github_actions
186188
end
187189
$stderr.puts 'Try running `puppet parser validate <file>`' if problems.any? { |p| p[:check] == :syntax }
188190
json
@@ -225,7 +227,7 @@ def run
225227

226228
# Public: Print any problems that were found out to stdout.
227229
#
228-
# Returns nothing.
230+
# Returns an array of problems. (Can be empty depending on configuration!)
229231
def print_problems
230232
report(@problems)
231233
end

lib/puppet-lint/bin.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'pathname'
22
require 'uri'
33
require 'puppet-lint/optparser'
4+
require 'puppet-lint/report/codeclimate'
45

56
# Internal: The logic of the puppet-lint bin script, contained in a class for
67
# ease of testing.
@@ -104,6 +105,10 @@ def run
104105
puts JSON.pretty_generate(all_problems)
105106
end
106107

108+
if PuppetLint.configuration.codeclimate_report_file
109+
PuppetLint::Report::CodeClimateReporter.write_report_file(all_problems, PuppetLint.configuration.codeclimate_report_file)
110+
end
111+
107112
return_val
108113
rescue PuppetLint::NoCodeError
109114
puts 'puppet-lint: no file specified or specified file does not exist'

lib/puppet-lint/configuration.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,6 @@ def defaults
153153
self.show_ignored = false
154154
self.ignore_paths = ['vendor/**/*.pp']
155155
self.github_actions = ENV.key?('GITHUB_ACTION')
156+
self.codeclimate_report_file = ENV['CODECLIMATE_REPORT_FILE']
156157
end
157158
end

lib/puppet-lint/optparser.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ def self.build(args = [])
102102
PuppetLint.configuration.sarif = true
103103
end
104104

105+
opts.on('--codeclimate-report-file FILE', 'Save a code climate compatible report to this file') do |file|
106+
PuppetLint.configuration.codeclimate_report_file = file
107+
end
108+
105109
opts.on('--list-checks', 'List available check names.') do
106110
PuppetLint.configuration.list_checks = true
107111
end

lib/puppet-lint/report/codeclimate.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
require 'digest'
4+
require 'json'
5+
6+
class PuppetLint::Report
7+
class CodeClimateReporter
8+
def self.write_report_file(problems, report_file)
9+
report = []
10+
problems.each do |messages|
11+
messages.each do |message|
12+
case message[:kind]
13+
when :warning
14+
severity = 'minor'
15+
when :error
16+
severity = 'major'
17+
else
18+
next
19+
end
20+
21+
issue = {
22+
type: :issue,
23+
check_name: message[:check],
24+
description: message[:message],
25+
categories: [:Style],
26+
severity: severity,
27+
location: {
28+
path: message[:path],
29+
lines: {
30+
begin: message[:line],
31+
end: message[:line],
32+
}
33+
},
34+
fingerprint: Digest::MD5.hexdigest(Marshal.dump(message))
35+
}
36+
37+
if message.key?(:description) && message.key?(:help_uri)
38+
issue[:content] = "#{message[:description].chomp('.')}. See [this page](#{message[:help_uri]}) for more information about the `#{message[:check]}` check."
39+
end
40+
report << issue
41+
end
42+
end
43+
File.write(report_file, JSON.pretty_generate(report))
44+
end
45+
end
46+
end

lib/puppet-lint/tasks/puppet-lint.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'puppet-lint/optparser'
55
require 'rake'
66
require 'rake/tasklib'
7+
require 'puppet-lint/report/codeclimate'
78

89
# Public: A Rake task that can be loaded and used with everything you need.
910
#
@@ -90,19 +91,25 @@ def define(args, &task_block)
9091
RakeFileUtils.send(:verbose, true) do
9192
linter = PuppetLint.new
9293
matched_files = FileList[@pattern]
94+
all_problems = []
9395

9496
matched_files = matched_files.exclude(*@ignore_paths)
9597

9698
matched_files.to_a.each do |puppet_file|
9799
next unless File.file?(puppet_file)
98100
linter.file = puppet_file
99101
linter.run
100-
linter.print_problems
102+
all_problems << linter.print_problems
101103

102104
if PuppetLint.configuration.fix && linter.problems.none? { |e| e[:check] == :syntax }
103105
IO.write(puppet_file, linter.manifest)
104106
end
105107
end
108+
109+
if PuppetLint.configuration.codeclimate_report_file
110+
PuppetLint::Report::CodeClimateReporter.write_report_file(all_problems, PuppetLint.configuration.codeclimate_report_file)
111+
end
112+
106113
abort if linter.errors? || (
107114
linter.warnings? && PuppetLint.configuration.fail_on_warnings
108115
)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[
2+
{
3+
"type": "issue",
4+
"check_name": "autoloader_layout",
5+
"description": "test::foo not in autoload module layout",
6+
"categories": [
7+
"Style"
8+
],
9+
"severity": "major",
10+
"location": {
11+
"path": "spec/fixtures/test/manifests/fail.pp",
12+
"lines": {
13+
"begin": 2,
14+
"end": 2
15+
}
16+
},
17+
"fingerprint": "7694e41686e47731a83238e2ed2cd519",
18+
"content": "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. See [this page](https://puppet.com/docs/puppet/latest/style_guide.html#separate-files) for more information about the `autoloader_layout` check."
19+
},
20+
{
21+
"type": "issue",
22+
"check_name": "parameter_order",
23+
"description": "optional parameter listed before required parameter",
24+
"categories": [
25+
"Style"
26+
],
27+
"severity": "minor",
28+
"location": {
29+
"path": "spec/fixtures/test/manifests/warning.pp",
30+
"lines": {
31+
"begin": 2,
32+
"end": 2
33+
}
34+
},
35+
"fingerprint": "e6d2f0563638599500bd81f8106a2cea",
36+
"content": "Test the manifest tokens for any parameterised classes or defined types that take parameters and record a warning if there are any optional parameters listed before required parameters. See [this page](https://puppet.com/docs/puppet/latest/style_guide.html#display-order-of-parameters) for more information about the `parameter_order` check."
37+
}
38+
]

spec/unit/puppet-lint/bin_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,30 @@ def initialize(args)
437437
end
438438
end
439439

440+
context 'when outputting code climate report' do
441+
let(:report_file) do
442+
Tempfile.new('report_file.json')
443+
end
444+
445+
let(:args) do
446+
[
447+
'--codeclimate-report-file',
448+
report_file.path,
449+
'spec/fixtures/test/manifests/fail.pp',
450+
'spec/fixtures/test/manifests/warning.pp',
451+
]
452+
end
453+
454+
after(:each) do
455+
report_file.unlink
456+
end
457+
458+
it 'creates a code climate report' do
459+
expect(bin.exitstatus).to eq(1)
460+
expect(FileUtils.compare_file(report_file.path, 'spec/fixtures/test/reports/code_climate.json')).to be_truthy
461+
end
462+
end
463+
440464
context 'when hiding ignored problems' do
441465
let(:args) do
442466
[

spec/unit/puppet-lint/configuration_spec.rb

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,18 @@
5555
end
5656

5757
expect(config.settings).to eq(
58-
'with_filename' => false,
59-
'fail_on_warnings' => false,
60-
'error_level' => :all,
61-
'log_format' => '',
62-
'sarif' => false,
63-
'with_context' => false,
64-
'fix' => false,
65-
'github_actions' => false,
66-
'show_ignored' => false,
67-
'json' => false,
68-
'ignore_paths' => ['vendor/**/*.pp'],
58+
'with_filename' => false,
59+
'fail_on_warnings' => false,
60+
'codeclimate_report_file' => nil,
61+
'error_level' => :all,
62+
'log_format' => '',
63+
'sarif' => false,
64+
'with_context' => false,
65+
'fix' => false,
66+
'github_actions' => false,
67+
'show_ignored' => false,
68+
'json' => false,
69+
'ignore_paths' => ['vendor/**/*.pp'],
6970
)
7071
end
7172

@@ -78,6 +79,15 @@
7879
expect(config.settings['github_actions']).to be(true)
7980
end
8081

82+
it 'defaults codeclimate_report_file to the CODECLIMATE_REPORT_FILE environment variable' do
83+
override_env do
84+
ENV['CODECLIMATE_REPORT_FILE'] = '/path/to/report.json'
85+
config.defaults
86+
end
87+
88+
expect(config.settings['codeclimate_report_file']).to eq('/path/to/report.json')
89+
end
90+
8191
def override_env
8292
old_env = ENV.to_h
8393
yield

0 commit comments

Comments
 (0)