Coverage check (#1)
* adapter for coverage * report adapter * generate coverage report * use inputs
This commit is contained in:
parent
f745a541cc
commit
6ae146e422
@ -8,4 +8,4 @@ Style/Documentation:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Metrics/LineLength:
|
Metrics/LineLength:
|
||||||
Max: 120
|
Max: 150
|
||||||
|
|||||||
33
README.md
33
README.md
@ -1,34 +1,21 @@
|
|||||||
## Brakeman github action
|
## Brakeman github action
|
||||||
|
|
||||||
Brakeman is a static analysis tool which checks Ruby on Rails applications for security vulnerabilities.
|
Check your coverage percentage.
|
||||||
[See more](https://github.com/presidentbeef/brakeman)
|
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
```yml
|
#### [Simplecov](https://github.com/colszowka/simplecov)
|
||||||
- name: Brakeman
|
|
||||||
uses: devmasx/brakeman-linter-action@v1.0.0
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom report
|
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
- name: Install gems
|
- uses: devmasx/coverage-check-action@coverage-check
|
||||||
run: |
|
with:
|
||||||
gem install brakeman -v 4.5.0
|
result_path: coverage/.last_run.json
|
||||||
- name: brakeman report
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
run: |
|
type: simplecov
|
||||||
brakeman -f json > tmp/brakeman.json || exit 0
|
min_coverage: 90
|
||||||
- name: Brakeman
|
|
||||||
uses: devmasx/brakeman-linter-action@v1.0.0
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
REPORT_PATH: tmp/brakeman.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|||||||
21
action.yml
21
action.yml
@ -1,9 +1,24 @@
|
|||||||
name: "Brakeman linter"
|
name: "Brakeman linter"
|
||||||
description: "A GitHub Action that lints your Ruby code with Brakeman!"
|
description: "A GitHub Action that lints your Ruby code with Brakeman!"
|
||||||
author: Miguel Savignano
|
author: Miguel Savignano
|
||||||
runs:
|
|
||||||
using: "docker"
|
|
||||||
image: "Dockerfile"
|
|
||||||
branding:
|
branding:
|
||||||
icon: "check-square"
|
icon: "check-square"
|
||||||
color: "red"
|
color: "red"
|
||||||
|
inputs:
|
||||||
|
type:
|
||||||
|
description: "simplecov | jest"
|
||||||
|
required: true
|
||||||
|
default: "simplecov"
|
||||||
|
token:
|
||||||
|
description: "Github token for create checks"
|
||||||
|
required: true
|
||||||
|
default: "World"
|
||||||
|
min_coverage:
|
||||||
|
description: "Minimum coverage"
|
||||||
|
default: "80"
|
||||||
|
result_path:
|
||||||
|
description: "Json with coverage result"
|
||||||
|
required: true
|
||||||
|
runs:
|
||||||
|
using: "docker"
|
||||||
|
image: "Dockerfile"
|
||||||
|
|||||||
33
lib/coverage_report.rb
Normal file
33
lib/coverage_report.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CoverageReport
|
||||||
|
def self.generate(type, report_path, data)
|
||||||
|
if type == 'simplecov'
|
||||||
|
simplecov(report_path, data)
|
||||||
|
elsif type == 'jest'
|
||||||
|
jest(report_path, data)
|
||||||
|
else
|
||||||
|
raise 'InvalidCoverageReportType'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.simplecov(report_path, data)
|
||||||
|
report = read_json(report_path)
|
||||||
|
minumum_percent = data[:min]
|
||||||
|
covered_percent = report.dig('result', 'covered_percent')
|
||||||
|
{ 'lines' => { 'covered_percent' => covered_percent, 'minumum_percent' => minumum_percent } }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.jest(report_path, data)
|
||||||
|
report = read_json(report_path)
|
||||||
|
minumum_percent = data[:min]
|
||||||
|
covered_percent = report.dig('result', 'covered_percent')
|
||||||
|
{ 'lines' => { 'covered_percent' => covered_percent, 'minumum_percent' => minumum_percent } }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def read_json(path)
|
||||||
|
JSON.parse(File.read(path))
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,13 +1,13 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class GithubCheckRunService
|
class GithubCheckRunService
|
||||||
CHECK_NAME = 'Brakeman'
|
CHECK_NAME = 'Coverage'
|
||||||
|
|
||||||
def initialize(report, github_data, report_adapter)
|
def initialize(report, github_data, report_adapter)
|
||||||
@report = report
|
@report = report
|
||||||
@github_data = github_data
|
@github_data = github_data
|
||||||
@report_adapter = report_adapter
|
@report_adapter = report_adapter
|
||||||
@client = GithubClient.new(@github_data[:token], user_agent: 'brakeman-action')
|
@client = GithubClient.new(@github_data[:token], user_agent: 'coverage-action')
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
|||||||
14
lib/index.rb
14
lib/index.rb
@ -6,6 +6,7 @@ require 'time'
|
|||||||
require_relative './report_adapter'
|
require_relative './report_adapter'
|
||||||
require_relative './github_check_run_service'
|
require_relative './github_check_run_service'
|
||||||
require_relative './github_client'
|
require_relative './github_client'
|
||||||
|
require_relative './coverage_report'
|
||||||
|
|
||||||
def read_json(path)
|
def read_json(path)
|
||||||
JSON.parse(File.read(path))
|
JSON.parse(File.read(path))
|
||||||
@ -14,16 +15,15 @@ end
|
|||||||
@event_json = read_json(ENV['GITHUB_EVENT_PATH']) if ENV['GITHUB_EVENT_PATH']
|
@event_json = read_json(ENV['GITHUB_EVENT_PATH']) if ENV['GITHUB_EVENT_PATH']
|
||||||
@github_data = {
|
@github_data = {
|
||||||
sha: ENV['GITHUB_SHA'],
|
sha: ENV['GITHUB_SHA'],
|
||||||
token: ENV['GITHUB_TOKEN'],
|
token: ENV['INPUT_TOKEN'],
|
||||||
owner: ENV['GITHUB_REPOSITORY_OWNER'] || @event_json.dig('repository', 'owner', 'login'),
|
owner: ENV['GITHUB_REPOSITORY_OWNER'] || @event_json.dig('repository', 'owner', 'login'),
|
||||||
repo: ENV['GITHUB_REPOSITORY_NAME'] || @event_json.dig('repository', 'name')
|
repo: ENV['GITHUB_REPOSITORY_NAME'] || @event_json.dig('repository', 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
@report =
|
@coverage_type = ENV['INPUT_TYPE']
|
||||||
if ENV['REPORT_PATH']
|
@report_path = ENV['INPUT_RESULT_PATH']
|
||||||
read_json(ENV['REPORT_PATH'])
|
@data = { min: ENV['INPUT_MIN_COVERAGE'] }
|
||||||
else
|
|
||||||
Dir.chdir(ENV['GITHUB_WORKSPACE']) { JSON.parse(`brakeman -f json`) }
|
@report = CoverageReport.generate(@coverage_type, @report_path, @data)
|
||||||
end
|
|
||||||
|
|
||||||
GithubCheckRunService.new(@report, @github_data, ReportAdapter).run
|
GithubCheckRunService.new(@report, @github_data, ReportAdapter).run
|
||||||
|
|||||||
@ -7,40 +7,29 @@ class ReportAdapter
|
|||||||
ANNOTATION_LEVEL = { notice: 'notice', warning: 'warning', failure: 'failure' }.freeze
|
ANNOTATION_LEVEL = { notice: 'notice', warning: 'warning', failure: 'failure' }.freeze
|
||||||
|
|
||||||
def conslusion(report)
|
def conslusion(report)
|
||||||
return CONCLUSION_TYPES[:failure] if security_warnings(report).positive?
|
lines_covered_percent(report) >= lines_minimum_percent(report).to_f ? CONCLUSION_TYPES[:success] : CONCLUSION_TYPES[:failure]
|
||||||
|
|
||||||
CONCLUSION_TYPES[:success]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def summary(report)
|
def summary(report)
|
||||||
"**Brakeman Report**:\n#{security_warnings(report)} security warnings\n#{check_table(report)}"
|
"**Coverage**:\n\n#{table_head}\n| Lines | #{lines_covered_percent(report)}% | #{lines_minimum_percent(report)}% |\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
def annotations(report)
|
def annotations(_report)
|
||||||
report['warnings'].map do |error|
|
[]
|
||||||
{
|
|
||||||
'path' => error['file'],
|
|
||||||
'start_line' => error['line'],
|
|
||||||
'end_line' => error['line'],
|
|
||||||
'annotation_level' => ANNOTATION_LEVEL[:warning],
|
|
||||||
'title' => "#{error['confidence']} - #{error['check_name']}",
|
|
||||||
'message' => error['message']
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_table(report)
|
def table_head
|
||||||
uniq_checks(report).reduce('') { |memo, check| memo + "- [#{check[:check_name]}](#{check[:link]})\n" }
|
"| Type | covered | minimum |\n| ----- | ------- | ------- |"
|
||||||
end
|
end
|
||||||
|
|
||||||
def uniq_checks(report)
|
def lines_covered_percent(report)
|
||||||
report['warnings'].map { |w| { check_name: w['check_name'], link: w['link'] } }.uniq { |w| w[:check_name] }
|
@lines_covered_percent ||= report.dig('lines', 'covered_percent')
|
||||||
end
|
end
|
||||||
|
|
||||||
def security_warnings(report)
|
def lines_minimum_percent(report)
|
||||||
report['scan_info']['security_warnings']
|
@lines_minimum_percent ||= report.dig('lines', 'minumum_percent')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
BIN
screenshots/fail.png
Normal file
BIN
screenshots/fail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 87 KiB |
BIN
screenshots/success.png
Normal file
BIN
screenshots/success.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
34
spec/fixtures/output/annotations.json
vendored
34
spec/fixtures/output/annotations.json
vendored
@ -1,34 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"path": "app/controllers/posts_controller.rb",
|
|
||||||
"start_line": 29,
|
|
||||||
"end_line": 29,
|
|
||||||
"annotation_level": "warning",
|
|
||||||
"title": "High - Evaluation",
|
|
||||||
"message": "User input in eval"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "app/controllers/posts_controller.rb",
|
|
||||||
"start_line": 18,
|
|
||||||
"end_line": 18,
|
|
||||||
"annotation_level": "warning",
|
|
||||||
"title": "High - MassAssignment",
|
|
||||||
"message": "Parameters should be whitelisted for mass assignment"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "app/controllers/posts_controller.rb",
|
|
||||||
"start_line": 19,
|
|
||||||
"end_line": 19,
|
|
||||||
"annotation_level": "warning",
|
|
||||||
"title": "High - MassAssignment",
|
|
||||||
"message": "Parameters should be whitelisted for mass assignment"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "app/controllers/posts_controller.rb",
|
|
||||||
"start_line": 13,
|
|
||||||
"end_line": 13,
|
|
||||||
"annotation_level": "warning",
|
|
||||||
"title": "Medium - SQL",
|
|
||||||
"message": "Possible SQL injection"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
10
spec/fixtures/output/summary.md
vendored
10
spec/fixtures/output/summary.md
vendored
@ -1,5 +1,5 @@
|
|||||||
**Brakeman Report**:
|
**Coverage**:
|
||||||
4 security warnings
|
|
||||||
- [Evaluation](https://brakemanscanner.org/docs/warning_types/dangerous_eval/)
|
| Type | covered | minimum |
|
||||||
- [MassAssignment](https://brakemanscanner.org/docs/warning_types/mass_assignment/)
|
| ----- | ------- | ------- |
|
||||||
- [SQL](https://brakemanscanner.org/docs/warning_types/sql_injection/)
|
| Lines | 80% | 80% |
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
require './spec/spec_helper'
|
require './spec/spec_helper'
|
||||||
|
|
||||||
describe GithubCheckRunService do
|
describe GithubCheckRunService do
|
||||||
let(:brakeman_report) { JSON(File.read('./spec/fixtures/report.json')) }
|
let(:report) do
|
||||||
|
{ 'lines' => { 'covered_percent' => 80, 'minumum_percent' => 80 } }
|
||||||
|
end
|
||||||
let(:github_data) { { sha: 'sha', token: 'token', owner: 'owner', repo: 'repository_name' } }
|
let(:github_data) { { sha: 'sha', token: 'token', owner: 'owner', repo: 'repository_name' } }
|
||||||
let(:service) { GithubCheckRunService.new(brakeman_report, github_data, ReportAdapter) }
|
let(:service) { GithubCheckRunService.new(report, github_data, ReportAdapter) }
|
||||||
|
|
||||||
it '#run' do
|
it '#run' do
|
||||||
stub_request(:any, 'https://api.github.com/repos/owner/repository_name/check-runs/id')
|
stub_request(:any, 'https://api.github.com/repos/owner/repository_name/check-runs/id')
|
||||||
|
|||||||
@ -3,12 +3,8 @@
|
|||||||
require './spec/spec_helper'
|
require './spec/spec_helper'
|
||||||
|
|
||||||
describe ReportAdapter do
|
describe ReportAdapter do
|
||||||
let(:brakeman_report) do
|
let(:report) do
|
||||||
JSON(File.read('./spec/fixtures/report.json'))
|
{ 'lines' => { 'covered_percent' => 80, 'minumum_percent' => 80 } }
|
||||||
end
|
|
||||||
|
|
||||||
let(:spec_annotations) do
|
|
||||||
JSON(File.read('./spec/fixtures/output/annotations.json'))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:spec_summary) do
|
let(:spec_summary) do
|
||||||
@ -18,17 +14,17 @@ describe ReportAdapter do
|
|||||||
let(:adapter) { ReportAdapter }
|
let(:adapter) { ReportAdapter }
|
||||||
|
|
||||||
it '.conslusion' do
|
it '.conslusion' do
|
||||||
result = adapter.conslusion(brakeman_report)
|
result = adapter.conslusion(report)
|
||||||
expect(result).to eq('failure')
|
expect(result).to eq('success')
|
||||||
end
|
end
|
||||||
|
|
||||||
it '.summary' do
|
it '.summary' do
|
||||||
result = adapter.summary(brakeman_report)
|
result = adapter.summary(report)
|
||||||
expect(result).to eq(spec_summary)
|
expect(result).to eq(spec_summary)
|
||||||
end
|
end
|
||||||
|
|
||||||
it '.annotations' do
|
it '.annotations' do
|
||||||
result = adapter.annotations(brakeman_report)
|
result = adapter.annotations(report)
|
||||||
expect(result).to eq(spec_annotations)
|
expect(result).to eq([])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user