puppet-lint 3.0.1 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -1
- data/lib/puppet-lint/bin.rb +5 -0
- data/lib/puppet-lint/configuration.rb +1 -0
- data/lib/puppet-lint/data.rb +13 -0
- data/lib/puppet-lint/optparser.rb +4 -0
- data/lib/puppet-lint/plugins/check_whitespace/trailing_whitespace.rb +1 -1
- data/lib/puppet-lint/plugins/legacy_facts/legacy_facts.rb +195 -0
- data/lib/puppet-lint/plugins/top_scope_facts/top_scope_facts.rb +38 -0
- data/lib/puppet-lint/report/codeclimate.rb +48 -0
- data/lib/puppet-lint/tasks/puppet-lint.rb +8 -1
- data/lib/puppet-lint/version.rb +1 -1
- data/lib/puppet-lint.rb +11 -7
- data/{.rubocop.yml → rubocop_baseline.yml} +13 -15
- data/spec/fixtures/test/reports/code_climate.json +38 -0
- data/spec/unit/puppet-lint/bin_spec.rb +26 -2
- data/spec/unit/puppet-lint/configuration_spec.rb +21 -11
- data/spec/unit/puppet-lint/data_spec.rb +36 -0
- data/spec/unit/puppet-lint/plugins/check_whitespace/trailing_whitespace_spec.rb +12 -0
- data/spec/unit/puppet-lint/plugins/legacy_facts/legacy_facts_spec.rb +447 -0
- data/spec/unit/puppet-lint/plugins/top_scope_facts/top_scope_facts_spec.rb +194 -0
- metadata +11 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8eaced98bf8f51da32089208cd7d628100e532e91d7323b77d04264584efecb
|
4
|
+
data.tar.gz: b4edfa7027559c4ccc1f0f25433558069857e9cbb6ef17ddcad85a9db1ee795f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b88dcb4eee4df0475c83764f456a3a9f2721bbee922ca8b7cb981225d07c4be674ffc4f929a45ec6e357b4c7ca702e1c88ffbbe44f4e52dd3f54836233852d4
|
7
|
+
data.tar.gz: dc0278aea71db1cab2c2de6788d58ab51c15a25567c912ec5fd3456c60a2c30eb1e43c2b55015bf242a1da9217c5f4c8c8c409dfb777504730f9ed3ae300520b
|
data/README.md
CHANGED
@@ -9,7 +9,9 @@ guide](http://puppet.com/docs/puppet/latest/style_guide.html). Puppet Lint valid
|
|
9
9
|
|
10
10
|
## Compatibility warning
|
11
11
|
|
12
|
-
|
12
|
+
Version 3.0.0 and above will no longer support Puppet 6 environments.
|
13
|
+
|
14
|
+
In cases where Puppet Lint is required in an environment with Puppet 6, we recommend pinning to version 2.5.2.
|
13
15
|
|
14
16
|
## Installation
|
15
17
|
|
@@ -213,6 +215,30 @@ There is a GitHub Actions action available to get linter feedback in workflows:
|
|
213
215
|
|
214
216
|
* [puppet-lint-action](https://github.com/marketplace/actions/puppet-lint-action)
|
215
217
|
|
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
|
+
|
216
242
|
## Options
|
217
243
|
|
218
244
|
See `puppet-lint --help` for a full list of command line options and checks.
|
data/lib/puppet-lint/bin.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'uri'
|
3
3
|
require 'puppet-lint/optparser'
|
4
|
+
require 'puppet-lint/report/codeclimate'
|
4
5
|
|
5
6
|
# Internal: The logic of the puppet-lint bin script, contained in a class for
|
6
7
|
# ease of testing.
|
@@ -104,6 +105,10 @@ class PuppetLint::Bin
|
|
104
105
|
puts JSON.pretty_generate(all_problems)
|
105
106
|
end
|
106
107
|
|
108
|
+
if PuppetLint.configuration.codeclimate_report_file
|
109
|
+
PuppetLint::Report::CodeClimateReporter.write_report_file(all_problems, PuppetLint.configuration.codeclimate_report_file)
|
110
|
+
end
|
111
|
+
|
107
112
|
return_val
|
108
113
|
rescue PuppetLint::NoCodeError
|
109
114
|
puts 'puppet-lint: no file specified or specified file does not exist'
|
data/lib/puppet-lint/data.rb
CHANGED
@@ -155,6 +155,18 @@ class PuppetLint::Data
|
|
155
155
|
end
|
156
156
|
end
|
157
157
|
|
158
|
+
# Internal: Determine if the given token contains a CLASSREF in
|
159
|
+
# the token chain..
|
160
|
+
#
|
161
|
+
# Returns a Boolean.
|
162
|
+
def classref?(token)
|
163
|
+
current_token = token
|
164
|
+
while (current_token = current_token.prev_code_token)
|
165
|
+
return true if current_token.type == :CLASSREF
|
166
|
+
return false if current_token.type == :NAME
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
158
170
|
# Internal: Calculate the positions of all resource declarations within the
|
159
171
|
# tokenised manifest. These positions only point to the content of the
|
160
172
|
# resource declarations, they do not include resource types or titles.
|
@@ -170,6 +182,7 @@ class PuppetLint::Data
|
|
170
182
|
result = []
|
171
183
|
tokens.select { |t| t.type == :COLON }.each do |colon_token|
|
172
184
|
next unless colon_token.next_code_token && colon_token.next_code_token.type != :LBRACE
|
185
|
+
next if classref?(colon_token)
|
173
186
|
|
174
187
|
rel_start_idx = tokens[marker..-1].index(colon_token)
|
175
188
|
break if rel_start_idx.nil?
|
@@ -102,6 +102,10 @@ class PuppetLint::OptParser
|
|
102
102
|
PuppetLint.configuration.sarif = true
|
103
103
|
end
|
104
104
|
|
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
|
+
|
105
109
|
opts.on('--list-checks', 'List available check names.') do
|
106
110
|
PuppetLint.configuration.list_checks = true
|
107
111
|
end
|
@@ -30,7 +30,7 @@ PuppetLint.new_check(:trailing_whitespace) do
|
|
30
30
|
|
31
31
|
prev_token = problem[:token].prev_token
|
32
32
|
next_token = problem[:token].next_token
|
33
|
-
prev_token.next_token = next_token
|
33
|
+
prev_token.next_token = next_token unless prev_token.nil?
|
34
34
|
next_token.prev_token = prev_token unless next_token.nil?
|
35
35
|
tokens.delete(problem[:token])
|
36
36
|
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# Public: A puppet-lint custom check to detect legacy facts.
|
2
|
+
#
|
3
|
+
# This check will optionally convert from legacy facts like $::operatingsystem
|
4
|
+
# or legacy hashed facts like $facts['operatingsystem'] to the
|
5
|
+
# new structured facts like $facts['os']['name'].
|
6
|
+
#
|
7
|
+
# This plugin was adopted in to puppet-lint from https://github.com/mmckinst/puppet-lint-legacy_facts-check
|
8
|
+
# Thanks to @mmckinst, @seanmil, @rodjek, @baurmatt, @bart2 and @joshcooper for the original work.
|
9
|
+
PuppetLint.new_check(:legacy_facts) do
|
10
|
+
LEGACY_FACTS_VAR_TYPES = Set[:VARIABLE, :UNENC_VARIABLE]
|
11
|
+
|
12
|
+
# These facts that can't be converted to new facts.
|
13
|
+
UNCONVERTIBLE_FACTS = ['memoryfree_mb', 'memorysize_mb', 'swapfree_mb',
|
14
|
+
'swapsize_mb', 'blockdevices', 'interfaces', 'zones',
|
15
|
+
'sshfp_dsa', 'sshfp_ecdsa', 'sshfp_ed25519',
|
16
|
+
'sshfp_rsa'].freeze
|
17
|
+
|
18
|
+
# These facts will depend on how a system is set up and can't just be
|
19
|
+
# enumerated like the EASY_FACTS below.
|
20
|
+
#
|
21
|
+
# For example a server might have two block devices named 'sda' and 'sdb' so
|
22
|
+
# there would be a $blockdeivce_sda_vendor and $blockdeivce_sdb_vendor fact
|
23
|
+
# for each device. Or it could have 26 block devices going all the way up to
|
24
|
+
# 'sdz'. There is no way to know what the possibilities are so we have to use
|
25
|
+
# a regex to match them.
|
26
|
+
REGEX_FACTS = [%r{^blockdevice_(?<devicename>.*)_(?<attribute>model|size|vendor)$},
|
27
|
+
%r{^(?<attribute>ipaddress|ipaddress6|macaddress|mtu|netmask|netmask6|network|network6)_(?<interface>.*)$},
|
28
|
+
%r{^processor(?<id>[0-9]+)$},
|
29
|
+
%r{^sp_(?<name>.*)$},
|
30
|
+
%r{^ssh(?<algorithm>dsa|ecdsa|ed25519|rsa)key$},
|
31
|
+
%r{^ldom_(?<name>.*)$},
|
32
|
+
%r{^zone_(?<name>.*)_(?<attribute>brand|iptype|name|uuid|id|path|status)$}].freeze
|
33
|
+
|
34
|
+
# These facts have a one to one correlation between a legacy fact and a new
|
35
|
+
# structured fact.
|
36
|
+
EASY_FACTS = {
|
37
|
+
'architecture' => "facts['os']['architecture']",
|
38
|
+
'augeasversion' => "facts['augeas']['version']",
|
39
|
+
'bios_release_date' => "facts['dmi']['bios']['release_date']",
|
40
|
+
'bios_vendor' => "facts['dmi']['bios']['vendor']",
|
41
|
+
'bios_version' => "facts['dmi']['bios']['version']",
|
42
|
+
'boardassettag' => "facts['dmi']['board']['asset_tag']",
|
43
|
+
'boardmanufacturer' => "facts['dmi']['board']['manufacturer']",
|
44
|
+
'boardproductname' => "facts['dmi']['board']['product']",
|
45
|
+
'boardserialnumber' => "facts['dmi']['board']['serial_number']",
|
46
|
+
'chassisassettag' => "facts['dmi']['chassis']['asset_tag']",
|
47
|
+
'chassistype' => "facts['dmi']['chassis']['type']",
|
48
|
+
'domain' => "facts['networking']['domain']",
|
49
|
+
'fqdn' => "facts['networking']['fqdn']",
|
50
|
+
'gid' => "facts['identity']['group']",
|
51
|
+
'hardwareisa' => "facts['processors']['isa']",
|
52
|
+
'hardwaremodel' => "facts['os']['hardware']",
|
53
|
+
'hostname' => "facts['networking']['hostname']",
|
54
|
+
'id' => "facts['identity']['user']",
|
55
|
+
'ipaddress' => "facts['networking']['ip']",
|
56
|
+
'ipaddress6' => "facts['networking']['ip6']",
|
57
|
+
'lsbdistcodename' => "facts['os']['distro']['codename']",
|
58
|
+
'lsbdistdescription' => "facts['os']['distro']['description']",
|
59
|
+
'lsbdistid' => "facts['os']['distro']['id']",
|
60
|
+
'lsbdistrelease' => "facts['os']['distro']['release']['full']",
|
61
|
+
'lsbmajdistrelease' => "facts['os']['distro']['release']['major']",
|
62
|
+
'lsbminordistrelease' => "facts['os']['distro']['release']['minor']",
|
63
|
+
'lsbrelease' => "facts['os']['distro']['release']['specification']",
|
64
|
+
'macaddress' => "facts['networking']['mac']",
|
65
|
+
'macosx_buildversion' => "facts['os']['build']",
|
66
|
+
'macosx_productname' => "facts['os']['product']",
|
67
|
+
'macosx_productversion' => "facts['os']['version']['full']",
|
68
|
+
'macosx_productversion_major' => "facts['os']['version']['major']",
|
69
|
+
'macosx_productversion_minor' => "facts['os']['version']['minor']",
|
70
|
+
'manufacturer' => "facts['dmi']['manufacturer']",
|
71
|
+
'memoryfree' => "facts['memory']['system']['available']",
|
72
|
+
'memorysize' => "facts['memory']['system']['total']",
|
73
|
+
'netmask' => "facts['networking']['netmask']",
|
74
|
+
'netmask6' => "facts['networking']['netmask6']",
|
75
|
+
'network' => "facts['networking']['network']",
|
76
|
+
'network6' => "facts['networking']['network6']",
|
77
|
+
'operatingsystem' => "facts['os']['name']",
|
78
|
+
'operatingsystemmajrelease' => "facts['os']['release']['major']",
|
79
|
+
'operatingsystemrelease' => "facts['os']['release']['full']",
|
80
|
+
'osfamily' => "facts['os']['family']",
|
81
|
+
'physicalprocessorcount' => "facts['processors']['physicalcount']",
|
82
|
+
'processorcount' => "facts['processors']['count']",
|
83
|
+
'productname' => "facts['dmi']['product']['name']",
|
84
|
+
'rubyplatform' => "facts['ruby']['platform']",
|
85
|
+
'rubysitedir' => "facts['ruby']['sitedir']",
|
86
|
+
'rubyversion' => "facts['ruby']['version']",
|
87
|
+
'selinux' => "facts['os']['selinux']['enabled']",
|
88
|
+
'selinux_config_mode' => "facts['os']['selinux']['config_mode']",
|
89
|
+
'selinux_config_policy' => "facts['os']['selinux']['config_policy']",
|
90
|
+
'selinux_current_mode' => "facts['os']['selinux']['current_mode']",
|
91
|
+
'selinux_enforced' => "facts['os']['selinux']['enforced']",
|
92
|
+
'selinux_policyversion' => "facts['os']['selinux']['policy_version']",
|
93
|
+
'serialnumber' => "facts['dmi']['product']['serial_number']",
|
94
|
+
'swapencrypted' => "facts['memory']['swap']['encrypted']",
|
95
|
+
'swapfree' => "facts['memory']['swap']['available']",
|
96
|
+
'swapsize' => "facts['memory']['swap']['total']",
|
97
|
+
'system32' => "facts['os']['windows']['system32']",
|
98
|
+
'uptime' => "facts['system_uptime']['uptime']",
|
99
|
+
'uptime_days' => "facts['system_uptime']['days']",
|
100
|
+
'uptime_hours' => "facts['system_uptime']['hours']",
|
101
|
+
'uptime_seconds' => "facts['system_uptime']['seconds']",
|
102
|
+
'uuid' => "facts['dmi']['product']['uuid']",
|
103
|
+
'xendomains' => "facts['xen']['domains']",
|
104
|
+
'zonename' => "facts['solaris_zones']['current']",
|
105
|
+
}.freeze
|
106
|
+
|
107
|
+
# A list of valid hash key token types
|
108
|
+
HASH_KEY_TYPES = Set[
|
109
|
+
:STRING, # Double quoted string
|
110
|
+
:SSTRING, # Single quoted string
|
111
|
+
:NAME, # Unquoted single word
|
112
|
+
].freeze
|
113
|
+
|
114
|
+
def check
|
115
|
+
tokens.select { |x| LEGACY_FACTS_VAR_TYPES.include?(x.type) }.each do |token|
|
116
|
+
fact_name = ''
|
117
|
+
|
118
|
+
# This matches legacy facts defined in the fact hash that use the top scope
|
119
|
+
# fact assignment.
|
120
|
+
if token.value.start_with?('::facts[')
|
121
|
+
fact_name = token.value.match(%r{::facts\['(.*)'\]})[1]
|
122
|
+
|
123
|
+
# This matches legacy facts defined in the fact hash.
|
124
|
+
elsif token.value.start_with?("facts['")
|
125
|
+
fact_name = token.value.match(%r{facts\['(.*)'\]})[1]
|
126
|
+
|
127
|
+
# This matches using legacy facts in a the new structured fact. For
|
128
|
+
# example this would match 'uuid' in $facts['uuid'] so it can be converted
|
129
|
+
# to facts['dmi']['product']['uuid']"
|
130
|
+
elsif token.value == 'facts'
|
131
|
+
fact_name = hash_key_for(token)
|
132
|
+
|
133
|
+
# Now we can get rid of top scopes. We don't need to
|
134
|
+
# preserve it because it won't work with the new structured facts.
|
135
|
+
elsif token.value.start_with?('::')
|
136
|
+
fact_name = token.value.sub(%r{^::}, '')
|
137
|
+
end
|
138
|
+
|
139
|
+
next unless EASY_FACTS.include?(fact_name) || UNCONVERTIBLE_FACTS.include?(fact_name) || fact_name.match(Regexp.union(REGEX_FACTS))
|
140
|
+
notify :warning, {
|
141
|
+
message: "legacy fact '#{fact_name}'",
|
142
|
+
line: token.line,
|
143
|
+
column: token.column,
|
144
|
+
token: token,
|
145
|
+
fact_name: fact_name,
|
146
|
+
}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# If the variable is using the $facts hash represented internally by multiple
|
151
|
+
# tokens, this helper simplifies accessing the hash key.
|
152
|
+
def hash_key_for(token)
|
153
|
+
lbrack_token = token.next_code_token
|
154
|
+
return '' unless lbrack_token && lbrack_token.type == :LBRACK
|
155
|
+
|
156
|
+
key_token = lbrack_token.next_code_token
|
157
|
+
return '' unless key_token && HASH_KEY_TYPES.include?(key_token.type)
|
158
|
+
|
159
|
+
key_token.value
|
160
|
+
end
|
161
|
+
|
162
|
+
def fix(problem)
|
163
|
+
fact_name = problem[:fact_name]
|
164
|
+
|
165
|
+
# Check if the variable is using the $facts hash represented internally by
|
166
|
+
# multiple tokens and remove the tokens for the old legacy key if so.
|
167
|
+
if problem[:token].value == 'facts'
|
168
|
+
loop do
|
169
|
+
t = problem[:token].next_token
|
170
|
+
remove_token(t)
|
171
|
+
break if t.type == :RBRACK
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
if EASY_FACTS.include?(fact_name)
|
176
|
+
problem[:token].value = EASY_FACTS[fact_name]
|
177
|
+
elsif fact_name.match(Regexp.union(REGEX_FACTS))
|
178
|
+
if (m = fact_name.match(%r{^blockdevice_(?<devicename>.*)_(?<attribute>model|size|vendor)$}))
|
179
|
+
problem[:token].value = "facts['disks']['" << m['devicename'] << "']['" << m['attribute'] << "']"
|
180
|
+
elsif (m = fact_name.match(%r{^(?<attribute>ipaddress|ipaddress6|macaddress|mtu|netmask|netmask6|network|network6)_(?<interface>.*)$}))
|
181
|
+
problem[:token].value = "facts['networking']['interfaces']['" << m['interface'] << "']['" << m['attribute'].sub('address', '') << "']"
|
182
|
+
elsif (m = fact_name.match(%r{^processor(?<id>[0-9]+)$}))
|
183
|
+
problem[:token].value = "facts['processors']['models'][" << m['id'] << ']'
|
184
|
+
elsif (m = fact_name.match(%r{^sp_(?<name>.*)$}))
|
185
|
+
problem[:token].value = "facts['system_profiler']['" << m['name'] << "']"
|
186
|
+
elsif (m = fact_name.match(%r{^ssh(?<algorithm>dsa|ecdsa|ed25519|rsa)key$}))
|
187
|
+
problem[:token].value = "facts['ssh']['" << m['algorithm'] << "']['key']"
|
188
|
+
elsif (m = fact_name.match(%r{^ldom_(?<name>.*)$}))
|
189
|
+
problem[:token].value = "facts['ldom']['" << m['name'] << "']"
|
190
|
+
elsif (m = fact_name.match(%r{^zone_(?<name>.*)_(?<attribute>brand|iptype|name|uuid|id|path|status)$}))
|
191
|
+
problem[:token].value = "facts['solaris_zones']['zones']['" << m['name'] << "']['" << m['attribute'] << "']"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Public: A puppet-lint plugin that will check for the use of top scope facts.
|
2
|
+
# For example, the fact `$facts['kernel']` should be used over
|
3
|
+
# `$::kernel`.
|
4
|
+
#
|
5
|
+
# The check only finds facts using the top-scope: ie it will find $::operatingsystem
|
6
|
+
# but not $operatingsystem. It also all top scope variables are facts.
|
7
|
+
# If you have top scope variables that aren't facts you should configure the
|
8
|
+
# linter to ignore them.
|
9
|
+
#
|
10
|
+
# You can whitelist top scope variables to ignore via the Rake task.
|
11
|
+
# You should insert the following line to your Rakefile.
|
12
|
+
# `PuppetLint.configuration.top_scope_variables = ['location', 'role']`
|
13
|
+
#
|
14
|
+
# This plugin was adopted in to puppet-lint from https://github.com/mmckinst/puppet-lint-top_scope_facts-check
|
15
|
+
# Thanks to @mmckinst, @seanmil and @alexjfisher for the original work.
|
16
|
+
PuppetLint.new_check(:top_scope_facts) do
|
17
|
+
TOP_SCOPE_FACTS_VAR_TYPES = Set[:VARIABLE, :UNENC_VARIABLE]
|
18
|
+
def check
|
19
|
+
whitelist = ['trusted', 'facts'] + (PuppetLint.configuration.top_scope_variables || [])
|
20
|
+
whitelist = whitelist.join('|')
|
21
|
+
tokens.select { |x| TOP_SCOPE_FACTS_VAR_TYPES.include?(x.type) }.each do |token|
|
22
|
+
next unless %r{^::}.match?(token.value)
|
23
|
+
next if %r{^::(#{whitelist})\[?}.match?(token.value)
|
24
|
+
next if %r{^::[a-z0-9_][a-zA-Z0-9_]+::}.match?(token.value)
|
25
|
+
|
26
|
+
notify :warning, {
|
27
|
+
message: 'top scope fact instead of facts hash',
|
28
|
+
line: token.line,
|
29
|
+
column: token.column,
|
30
|
+
token: token,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def fix(problem)
|
36
|
+
problem[:token].value = "facts['" + problem[:token].value.sub(%r{^::}, '') + "']"
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class PuppetLint::Report
|
7
|
+
# Formats problems and writes them to a file as a code climate compatible report.
|
8
|
+
class CodeClimateReporter
|
9
|
+
def self.write_report_file(problems, report_file)
|
10
|
+
report = []
|
11
|
+
problems.each do |messages|
|
12
|
+
messages.each do |message|
|
13
|
+
case message[:kind]
|
14
|
+
when :warning
|
15
|
+
severity = 'minor'
|
16
|
+
when :error
|
17
|
+
severity = 'major'
|
18
|
+
else
|
19
|
+
next
|
20
|
+
end
|
21
|
+
|
22
|
+
issue = {
|
23
|
+
type: :issue,
|
24
|
+
check_name: message[:check],
|
25
|
+
description: message[:message],
|
26
|
+
categories: [:Style],
|
27
|
+
severity: severity,
|
28
|
+
location: {
|
29
|
+
path: message[:path],
|
30
|
+
lines: {
|
31
|
+
begin: message[:line],
|
32
|
+
end: message[:line],
|
33
|
+
}
|
34
|
+
},
|
35
|
+
}
|
36
|
+
|
37
|
+
issue[:fingerprint] = Digest::MD5.hexdigest(Marshal.dump(issue))
|
38
|
+
|
39
|
+
if message.key?(:description) && message.key?(:help_uri)
|
40
|
+
issue[:content] = "#{message[:description].chomp('.')}. See [this page](#{message[:help_uri]}) for more information about the `#{message[:check]}` check."
|
41
|
+
end
|
42
|
+
report << issue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
File.write(report_file, "#{JSON.pretty_generate(report)}\n")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -4,6 +4,7 @@ require 'puppet-lint'
|
|
4
4
|
require 'puppet-lint/optparser'
|
5
5
|
require 'rake'
|
6
6
|
require 'rake/tasklib'
|
7
|
+
require 'puppet-lint/report/codeclimate'
|
7
8
|
|
8
9
|
# Public: A Rake task that can be loaded and used with everything you need.
|
9
10
|
#
|
@@ -90,6 +91,7 @@ class PuppetLint::RakeTask < ::Rake::TaskLib
|
|
90
91
|
RakeFileUtils.send(:verbose, true) do
|
91
92
|
linter = PuppetLint.new
|
92
93
|
matched_files = FileList[@pattern]
|
94
|
+
all_problems = []
|
93
95
|
|
94
96
|
matched_files = matched_files.exclude(*@ignore_paths)
|
95
97
|
|
@@ -97,12 +99,17 @@ class PuppetLint::RakeTask < ::Rake::TaskLib
|
|
97
99
|
next unless File.file?(puppet_file)
|
98
100
|
linter.file = puppet_file
|
99
101
|
linter.run
|
100
|
-
linter.print_problems
|
102
|
+
all_problems << linter.print_problems
|
101
103
|
|
102
104
|
if PuppetLint.configuration.fix && linter.problems.none? { |e| e[:check] == :syntax }
|
103
105
|
IO.write(puppet_file, linter.manifest)
|
104
106
|
end
|
105
107
|
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
|
+
|
106
113
|
abort if linter.errors? || (
|
107
114
|
linter.warnings? && PuppetLint.configuration.fail_on_warnings
|
108
115
|
)
|
data/lib/puppet-lint/version.rb
CHANGED
data/lib/puppet-lint.rb
CHANGED
@@ -124,6 +124,7 @@ class PuppetLint
|
|
124
124
|
puts format % message
|
125
125
|
|
126
126
|
puts " #{message[:reason]}" if message[:kind] == :ignored && !message[:reason].nil?
|
127
|
+
print_context(message)
|
127
128
|
end
|
128
129
|
|
129
130
|
# Internal: Format a problem message and print it to STDOUT so GitHub Actions
|
@@ -154,7 +155,8 @@ class PuppetLint
|
|
154
155
|
def print_context(message)
|
155
156
|
return if message[:check] == 'documentation'
|
156
157
|
return if message[:kind] == :fixed
|
157
|
-
line =
|
158
|
+
line = message[:context]
|
159
|
+
return unless line
|
158
160
|
offset = line.index(%r{\S}) || 1
|
159
161
|
puts "\n #{line.strip}"
|
160
162
|
printf("%#{message[:column] + 2 - offset}s\n\n", '^')
|
@@ -168,6 +170,8 @@ class PuppetLint
|
|
168
170
|
# Returns array of problem.
|
169
171
|
def report(problems)
|
170
172
|
json = []
|
173
|
+
print_stdout = !(configuration.json || configuration.sarif)
|
174
|
+
|
171
175
|
problems.each do |message|
|
172
176
|
next if message[:kind] == :ignored && !PuppetLint.configuration.show_ignored
|
173
177
|
|
@@ -175,12 +179,12 @@ class PuppetLint
|
|
175
179
|
|
176
180
|
next unless message[:kind] == :fixed || [message[:kind], :all].include?(configuration.error_level)
|
177
181
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
+
message[:context] = get_context(message) if configuration.with_context
|
183
|
+
|
184
|
+
json << message
|
185
|
+
|
186
|
+
if print_stdout
|
182
187
|
format_message(message)
|
183
|
-
print_context(message) if configuration.with_context
|
184
188
|
print_github_annotation(message) if configuration.github_actions
|
185
189
|
end
|
186
190
|
end
|
@@ -225,7 +229,7 @@ class PuppetLint
|
|
225
229
|
|
226
230
|
# Public: Print any problems that were found out to stdout.
|
227
231
|
#
|
228
|
-
# Returns
|
232
|
+
# Returns an array of problems.
|
229
233
|
def print_problems
|
230
234
|
report(@problems)
|
231
235
|
end
|
@@ -1,24 +1,22 @@
|
|
1
1
|
---
|
2
2
|
require:
|
3
|
-
- rubocop-performance
|
4
|
-
- rubocop-rspec
|
3
|
+
- rubocop-performance
|
4
|
+
- rubocop-rspec
|
5
5
|
AllCops:
|
6
|
+
TargetRubyVersion: '2.7'
|
6
7
|
DisplayCopNames: true
|
7
|
-
TargetRubyVersion: '2.6'
|
8
8
|
SuggestExtensions: false
|
9
|
-
Include:
|
10
|
-
- "**/*.rb"
|
11
9
|
Exclude:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
- "bin/*"
|
11
|
+
- ".vendor/**/*"
|
12
|
+
- "**/Gemfile"
|
13
|
+
- "**/Rakefile"
|
14
|
+
- "pkg/**/*"
|
15
|
+
- "spec/fixtures/**/*"
|
16
|
+
- "vendor/**/*"
|
17
|
+
- "**/Puppetfile"
|
18
|
+
- "**/Vagrantfile"
|
19
|
+
- "**/Guardfile"
|
22
20
|
Layout/LineLength:
|
23
21
|
Description: People have wide screens, use them.
|
24
22
|
Max: 200
|
@@ -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": "f12bce7a776454ab9ffac2d20dcc34ba",
|
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": "e34cf289e008446b633d1be7cf58120e",
|
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
|
+
]
|
@@ -378,7 +378,7 @@ describe PuppetLint::Bin do
|
|
378
378
|
if respond_to?(:include_json)
|
379
379
|
is_expected.to include_json([[{ 'KIND' => 'WARNING' }]])
|
380
380
|
else
|
381
|
-
is_expected.to match(%r{\[\n \{})
|
381
|
+
is_expected.to match(%r{\[\n \[\n \{})
|
382
382
|
end
|
383
383
|
end
|
384
384
|
end
|
@@ -397,7 +397,7 @@ describe PuppetLint::Bin do
|
|
397
397
|
if respond_to?(:include_json)
|
398
398
|
is_expected.to include_json([[{ 'KIND' => 'ERROR' }], [{ 'KIND' => 'WARNING' }]])
|
399
399
|
else
|
400
|
-
is_expected.to match(%r{\[\n \{})
|
400
|
+
is_expected.to match(%r{\[\n \[\n \{})
|
401
401
|
end
|
402
402
|
end
|
403
403
|
end
|
@@ -437,6 +437,30 @@ describe PuppetLint::Bin do
|
|
437
437
|
end
|
438
438
|
end
|
439
439
|
|
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
|
+
|
440
464
|
context 'when hiding ignored problems' do
|
441
465
|
let(:args) do
|
442
466
|
[
|