Merge pull request #181 from tspascoal/add-summary
Show vulnerabities and license information on the job summary.
This commit is contained in:
@@ -5,8 +5,14 @@ raise an error if any vulnerabilities or invalid licenses are being introduced.
|
|||||||
|
|
||||||
The action is available for all public repositories, as well as private repositories that have GitHub Advanced Security licensed.
|
The action is available for all public repositories, as well as private repositories that have GitHub Advanced Security licensed.
|
||||||
|
|
||||||
|
You can see the results on the job logs
|
||||||
|
|
||||||
<img width="854" alt="Screen Shot 2022-03-31 at 1 10 51 PM" src="https://user-images.githubusercontent.com/2161/161042286-b22d7dd3-13cb-458d-8744-ce70ed9bf562.png">
|
<img width="854" alt="Screen Shot 2022-03-31 at 1 10 51 PM" src="https://user-images.githubusercontent.com/2161/161042286-b22d7dd3-13cb-458d-8744-ce70ed9bf562.png">
|
||||||
|
|
||||||
|
or on the job summary
|
||||||
|
|
||||||
|
<img src="https://user-images.githubusercontent.com/7847935/182871416-50332bbb-b279-4621-a136-ca72a4314301.png">
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
**Please keep in mind that you need a [GitHub Advanced Security](https://docs.github.com/en/enterprise-cloud@latest/get-started/learning-about-github/about-github-advanced-security) license if you're running this action on private repositories.**
|
**Please keep in mind that you need a [GitHub Advanced Security](https://docs.github.com/en/enterprise-cloud@latest/get-started/learning-about-github/about-github-advanced-security) license if you're running this action on private repositories.**
|
||||||
@@ -144,15 +150,6 @@ to filter. A couple of examples:
|
|||||||
|
|
||||||
**Important**
|
**Important**
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
- The action will only accept one of the two parameters; an error will
|
|
||||||
be raised if you provide both.
|
|
||||||
- By default both parameters are empty (no license checking is
|
|
||||||
performed).
|
|
||||||
- We don't have license information for all of your dependents. If we
|
|
||||||
can't detect the license for a dependency **we will inform you, but the
|
|
||||||
action won't fail**.
|
|
||||||
=======
|
|
||||||
* Checking for licenses is not supported on GHES.
|
* Checking for licenses is not supported on GHES.
|
||||||
* The action will only accept one of the two parameters; an error will
|
* The action will only accept one of the two parameters; an error will
|
||||||
be raised if you provide both.
|
be raised if you provide both.
|
||||||
@@ -161,7 +158,6 @@ performed).
|
|||||||
* We don't have license information for all of your dependents. If we
|
* We don't have license information for all of your dependents. If we
|
||||||
can't detect the license for a dependency **we will inform you, but the
|
can't detect the license for a dependency **we will inform you, but the
|
||||||
action won't fail**.
|
action won't fail**.
|
||||||
>>>>>>> main
|
|
||||||
|
|
||||||
## Blocking pull requests
|
## Blocking pull requests
|
||||||
|
|
||||||
|
|||||||
+14156
-14000
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+21
-10
@@ -7,8 +7,10 @@ import {Change, Severity} from './schemas'
|
|||||||
import {readConfig} from '../src/config'
|
import {readConfig} from '../src/config'
|
||||||
import {filterChangesBySeverity} from '../src/filter'
|
import {filterChangesBySeverity} from '../src/filter'
|
||||||
import {getDeniedLicenseChanges} from './licenses'
|
import {getDeniedLicenseChanges} from './licenses'
|
||||||
|
import * as summary from './summary'
|
||||||
import {getRefs} from './git-refs'
|
import {getRefs} from './git-refs'
|
||||||
|
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const config = readConfig()
|
const config = readConfig()
|
||||||
@@ -29,27 +31,32 @@ async function run(): Promise<void> {
|
|||||||
deny: config.deny_licenses
|
deny: config.deny_licenses
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredChanges = filterChangesBySeverity(
|
const addedChanges = filterChangesBySeverity(
|
||||||
minSeverity as Severity,
|
minSeverity as Severity,
|
||||||
changes
|
changes
|
||||||
)
|
).filter(
|
||||||
|
change =>
|
||||||
for (const change of filteredChanges) {
|
|
||||||
if (
|
|
||||||
change.change_type === 'added' &&
|
change.change_type === 'added' &&
|
||||||
change.vulnerabilities !== undefined &&
|
change.vulnerabilities !== undefined &&
|
||||||
change.vulnerabilities.length > 0
|
change.vulnerabilities.length > 0
|
||||||
) {
|
)
|
||||||
printChangeVulnerabilities(change)
|
|
||||||
failed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [licenseErrors, unknownLicenses] = getDeniedLicenseChanges(
|
const [licenseErrors, unknownLicenses] = getDeniedLicenseChanges(
|
||||||
changes,
|
changes,
|
||||||
licenses
|
licenses
|
||||||
)
|
)
|
||||||
|
|
||||||
|
summary.addSummaryToSummary(addedChanges, licenseErrors, unknownLicenses)
|
||||||
|
|
||||||
|
if (addedChanges.length > 0) {
|
||||||
|
for (const change of addedChanges) {
|
||||||
|
printChangeVulnerabilities(change)
|
||||||
|
}
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity || '')
|
||||||
|
|
||||||
if (licenseErrors.length > 0) {
|
if (licenseErrors.length > 0) {
|
||||||
printLicensesError(licenseErrors)
|
printLicensesError(licenseErrors)
|
||||||
core.setFailed('Dependency review detected incompatible licenses.')
|
core.setFailed('Dependency review detected incompatible licenses.')
|
||||||
@@ -57,6 +64,8 @@ async function run(): Promise<void> {
|
|||||||
|
|
||||||
printNullLicenses(unknownLicenses)
|
printNullLicenses(unknownLicenses)
|
||||||
|
|
||||||
|
summary.addLicensesToSummary(licenseErrors, unknownLicenses, config)
|
||||||
|
|
||||||
if (failed) {
|
if (failed) {
|
||||||
core.setFailed('Dependency review detected vulnerable packages.')
|
core.setFailed('Dependency review detected vulnerable packages.')
|
||||||
} else {
|
} else {
|
||||||
@@ -80,6 +89,8 @@ async function run(): Promise<void> {
|
|||||||
core.setFailed('Unexpected fatal error')
|
core.setFailed('Unexpected fatal error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
await core.summary.write()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
import * as core from '@actions/core'
|
||||||
|
import {ConfigurationOptions, Change, Changes} from './schemas'
|
||||||
|
import {SummaryTableRow} from '@actions/core/lib/summary'
|
||||||
|
|
||||||
|
export function addSummaryToSummary(
|
||||||
|
addedPackages: Changes,
|
||||||
|
licenseErrors: Change[],
|
||||||
|
unknownLicenses: Change[]
|
||||||
|
): void {
|
||||||
|
core.summary
|
||||||
|
.addHeading('Dependency Review')
|
||||||
|
.addRaw(
|
||||||
|
`We found ${addedPackages.length} vulnerable package(s), ${licenseErrors.length} package(s) with incompatible licenses, and ${unknownLicenses.length} package(s) with unknown licenses.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addChangeVulnerabilitiesToSummary(
|
||||||
|
addedPackages: Changes,
|
||||||
|
severity: string
|
||||||
|
): void {
|
||||||
|
const rows: SummaryTableRow[] = []
|
||||||
|
|
||||||
|
const manifests = getManifests(addedPackages)
|
||||||
|
|
||||||
|
core.summary
|
||||||
|
.addHeading('Vulnerabilities')
|
||||||
|
.addQuote(
|
||||||
|
`Vulnerabilites were filtered by mininum severity <strong>${severity}</strong>.`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (addedPackages.length === 0) {
|
||||||
|
core.summary.addQuote('No vulnerabilities found in added packages.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const manifest of manifests) {
|
||||||
|
for (const change of addedPackages.filter(
|
||||||
|
pkg => pkg.manifest === manifest
|
||||||
|
)) {
|
||||||
|
let previous_package = ''
|
||||||
|
let previous_version = ''
|
||||||
|
for (const vuln of change.vulnerabilities) {
|
||||||
|
const sameAsPrevious =
|
||||||
|
previous_package === change.name &&
|
||||||
|
previous_version === change.version
|
||||||
|
|
||||||
|
if (!sameAsPrevious) {
|
||||||
|
rows.push([
|
||||||
|
renderUrl(change.source_repository_url, change.name),
|
||||||
|
change.version,
|
||||||
|
renderUrl(vuln.advisory_url, vuln.advisory_summary),
|
||||||
|
vuln.severity
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
rows.push([
|
||||||
|
{data: '', colspan: '2'},
|
||||||
|
renderUrl(vuln.advisory_url, vuln.advisory_summary),
|
||||||
|
vuln.severity
|
||||||
|
])
|
||||||
|
}
|
||||||
|
previous_package = change.name
|
||||||
|
previous_version = change.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core.summary.addHeading(`<em>${manifest}</em>`, 3).addTable([
|
||||||
|
[
|
||||||
|
{data: 'Name', header: true},
|
||||||
|
{data: 'Version', header: true},
|
||||||
|
{data: 'Vulnerability', header: true},
|
||||||
|
{data: 'Severity', header: true}
|
||||||
|
],
|
||||||
|
...rows
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addLicensesToSummary(
|
||||||
|
licenseErrors: Change[],
|
||||||
|
unknownLicenses: Change[],
|
||||||
|
config: ConfigurationOptions
|
||||||
|
): void {
|
||||||
|
core.summary.addHeading('Licenses')
|
||||||
|
|
||||||
|
if (config.allow_licenses && config.allow_licenses.length > 0) {
|
||||||
|
core.summary.addQuote(
|
||||||
|
`<strong>Allowed Licenses</strong>: ${config.allow_licenses.join(', ')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.deny_licenses && config.deny_licenses.length > 0) {
|
||||||
|
core.summary.addQuote(
|
||||||
|
`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (licenseErrors.length === 0 && unknownLicenses.length === 0) {
|
||||||
|
core.summary.addQuote('No license violations detected.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (licenseErrors.length > 0) {
|
||||||
|
const rows: SummaryTableRow[] = []
|
||||||
|
const manifests = getManifests(licenseErrors)
|
||||||
|
|
||||||
|
core.summary.addHeading('Incompatible Licenses', 3).addSeparator()
|
||||||
|
|
||||||
|
for (const manifest of manifests) {
|
||||||
|
core.summary.addHeading(`<em>${manifest}</em>`, 4)
|
||||||
|
|
||||||
|
for (const change of licenseErrors.filter(
|
||||||
|
pkg => pkg.manifest === manifest
|
||||||
|
)) {
|
||||||
|
rows.push([
|
||||||
|
renderUrl(change.source_repository_url, change.name),
|
||||||
|
change.version,
|
||||||
|
change.license || ''
|
||||||
|
])
|
||||||
|
}
|
||||||
|
core.summary.addTable([['Package', 'Version', 'License'], ...rows])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
core.summary.addQuote('No license violations detected.')
|
||||||
|
}
|
||||||
|
|
||||||
|
core.debug(`found ${unknownLicenses.length} unknown licenses`)
|
||||||
|
|
||||||
|
if (unknownLicenses.length > 0) {
|
||||||
|
const rows: SummaryTableRow[] = []
|
||||||
|
const manifests = getManifests(unknownLicenses)
|
||||||
|
|
||||||
|
core.debug(
|
||||||
|
`found ${manifests.entries.length} manifests for unknown licenses`
|
||||||
|
)
|
||||||
|
|
||||||
|
core.summary.addHeading('Unknown Licenses', 3).addSeparator()
|
||||||
|
|
||||||
|
for (const manifest of manifests) {
|
||||||
|
core.summary.addHeading(`<em>${manifest}</em>`, 4)
|
||||||
|
|
||||||
|
for (const change of unknownLicenses.filter(
|
||||||
|
pkg => pkg.manifest === manifest
|
||||||
|
)) {
|
||||||
|
rows.push([
|
||||||
|
renderUrl(change.source_repository_url, change.name),
|
||||||
|
change.version
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
core.summary.addTable([['Package', 'Version'], ...rows])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getManifests(changes: Changes): Set<string> {
|
||||||
|
return new Set(changes.flatMap(c => c.manifest))
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUrl(url: string | null, text: string): string {
|
||||||
|
if (url) {
|
||||||
|
return `<a href="${url}">${text}</a>`
|
||||||
|
} else {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user