diff --git a/lib/puppet-lint.rb b/lib/puppet-lint.rb index 3b30d1cf..ef54a8a2 100644 --- a/lib/puppet-lint.rb +++ b/lib/puppet-lint.rb @@ -7,6 +7,7 @@ require 'puppet-lint/configuration' require 'puppet-lint/data' require 'puppet-lint/checks' +require 'puppet-lint/report/github' require 'puppet-lint/bin' require 'puppet-lint/monkeypatches' @@ -121,6 +122,16 @@ def format_message(message) puts " #{message[:reason]}" if message[:kind] == :ignored && !message[:reason].nil? end + # Internal: Format a problem message and print it to STDOUT so GitHub Actions + # recognizes it as an annotation. + # + # message - A Hash containing all the information about a problem. + # + # Returns nothing. + def print_github_annotation(message) + puts PuppetLint::Report::GitHubActionsReporter.format_problem(path, message) + end + # Internal: Get the line of the manifest on which the problem was found # # message - A Hash containing all the information about a problem. @@ -158,15 +169,17 @@ def report(problems) message[:KIND] = message[:kind].to_s.upcase - if message[:kind] == :fixed || [message[:kind], :all].include?(configuration.error_level) - if configuration.json - message['context'] = get_context(message) if configuration.with_context - json << message - else - format_message(message) - print_context(message) if configuration.with_context - end + next unless message[:kind] == :fixed || [message[:kind], :all].include?(configuration.error_level) + + if configuration.json + message['context'] = get_context(message) if configuration.with_context + json << message + else + format_message(message) + print_context(message) if configuration.with_context end + + print_github_annotation(message) if configuration.github_actions end puts JSON.pretty_generate(json) if configuration.json diff --git a/lib/puppet-lint/configuration.rb b/lib/puppet-lint/configuration.rb index 8912ab03..5dc8b7a2 100644 --- a/lib/puppet-lint/configuration.rb +++ b/lib/puppet-lint/configuration.rb @@ -152,6 +152,7 @@ def defaults self.json = false self.show_ignored = false self.ignore_paths = ['vendor/**/*.pp'] + self.github_actions = ENV.key?('GITHUB_ACTION') end end end diff --git a/lib/puppet-lint/report/github.rb b/lib/puppet-lint/report/github.rb new file mode 100644 index 00000000..7ed3e2e3 --- /dev/null +++ b/lib/puppet-lint/report/github.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class PuppetLint + module Report + # This formatter formats report data as GitHub Workflow commands resulting + # in GitHub check annotations when run within GitHub Actions. + class GitHubActionsReporter + ESCAPE_MAP = { '%' => '%25', "\n" => '%0A', "\r" => '%0D' }.freeze + + def self.format_problem(file, problem) + format( + "\n::%s file=%s,line=%d,col=%d::%s (check: %s)\n", + :severity => problem[:kind], + :file => file, + :line => problem[:line], + :column => problem[:column], + :message => github_escape(problem[:message]), + :check => problem[:check] + ) + end + + def self.github_escape(string) + string.gsub(Regexp.union(ESCAPE_MAP.keys), ESCAPE_MAP) + end + end + end +end diff --git a/spec/puppet-lint/configuration_spec.rb b/spec/puppet-lint/configuration_spec.rb index 82b3bc8d..cda6c0ce 100644 --- a/spec/puppet-lint/configuration_spec.rb +++ b/spec/puppet-lint/configuration_spec.rb @@ -49,7 +49,10 @@ end it 'should be able to set sane defaults' do - subject.defaults + override_env do + ENV.delete('GITHUB_ACTION') + subject.defaults + end expect(subject.settings).to eq( 'with_filename' => false, @@ -58,9 +61,27 @@ 'log_format' => '', 'with_context' => false, 'fix' => false, + 'github_actions' => false, 'show_ignored' => false, 'json' => false, 'ignore_paths' => ['vendor/**/*.pp'] ) end + + it 'detects github actions' do + override_env do + ENV['GITHUB_ACTION'] = 'action' + subject.defaults + end + + expect(subject.settings['github_actions']).to be(true) + end + + def override_env + old_env = ENV.clone + yield + ensure + ENV.clear + ENV.update(old_env) + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bb8ed748..1628629d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,6 @@ +# Disable GitHub Actions reporting since it breaks the test suite +ENV.delete('GITHUB_ACTION') + if ENV['COVERAGE'] == 'yes' && RUBY_VERSION.start_with?('2.6.') require 'simplecov' SimpleCov.start do