2022-08-01 21:07:02 +01:00
import * as core from '@actions/core'
2023-08-02 16:17:51 +02:00
import { ConfigurationOptions , Changes , Change } from './schemas'
2022-08-01 21:07:02 +01:00
import { SummaryTableRow } from '@actions/core/lib/summary'
2023-03-02 07:43:23 +00:00
import { InvalidLicenseChanges , InvalidLicenseChangeTypes } from './licenses'
2022-09-27 12:25:12 +02:00
import { groupDependenciesByManifest , getManifestsSet , renderUrl } from './utils'
2022-08-01 21:07:02 +01:00
2023-02-22 14:05:52 +00:00
const icons = {
check : '✅' ,
cross : '❌' ,
warning : '⚠️'
}
export function addSummaryToSummary (
2023-02-28 12:28:20 +00:00
vulnerableChanges : Changes ,
2023-02-22 14:05:52 +00:00
invalidLicenseChanges : InvalidLicenseChanges ,
2023-08-02 15:48:28 +02:00
deniedChanges : Changes ,
2023-02-22 14:05:52 +00:00
config : ConfigurationOptions
) : void {
2023-02-28 12:28:20 +00:00
core . summary . addHeading ( 'Dependency Review' , 1 )
2023-02-22 14:05:52 +00:00
if (
2023-02-28 12:28:20 +00:00
vulnerableChanges . length === 0 &&
2023-08-02 16:17:51 +02:00
countLicenseIssues ( invalidLicenseChanges ) === 0 &&
deniedChanges . length === 0
2023-02-22 14:05:52 +00:00
) {
if ( ! config . license_check ) {
core . summary . addRaw ( ` ${ icons . check } No vulnerabilities found.` )
} else if ( ! config . vulnerability_check ) {
core . summary . addRaw ( ` ${ icons . check } No license issues found.` )
} else {
core . summary . addRaw (
` ${ icons . check } No vulnerabilities or license issues found.`
)
}
2023-02-27 16:05:59 +00:00
return
2023-02-22 14:05:52 +00:00
}
2023-02-27 16:05:59 +00:00
core . summary
. addRaw ( 'The following issues were found:' )
. addList ([
...( config . vulnerability_check
? [
2023-02-28 12:28:20 +00:00
` ${ checkOrFailIcon ( vulnerableChanges . length ) } ${
vulnerableChanges . length
2023-02-27 16:05:59 +00:00
} vulnerable package(s)`
]
: []),
...( config . license_check
? [
` ${ checkOrFailIcon ( invalidLicenseChanges . forbidden . length ) } ${
invalidLicenseChanges . forbidden . length
} package(s) with incompatible licenses` ,
2023-02-28 12:28:20 +00:00
` ${ checkOrFailIcon ( invalidLicenseChanges . unresolved . length ) } ${
invalidLicenseChanges . unresolved . length
} package(s) with invalid SPDX license definitions` ,
2023-02-27 16:05:59 +00:00
` ${ checkOrWarnIcon ( invalidLicenseChanges . unlicensed . length ) } ${
invalidLicenseChanges . unlicensed . length
} package(s) with unknown licenses.`
]
2023-08-02 16:17:51 +02:00
: []),
...( deniedChanges . length > 0
? [
` ${ checkOrWarnIcon ( deniedChanges . length ) } ${
deniedChanges . length
} package(s) denied.`
]
2023-02-27 16:05:59 +00:00
: [])
])
2023-02-28 12:28:20 +00:00
. addRaw ( 'See the Details below.' )
2022-08-01 21:07:02 +01:00
}
export function addChangeVulnerabilitiesToSummary (
2023-02-28 12:28:20 +00:00
vulnerableChanges : Changes ,
2022-08-01 21:07:02 +01:00
severity : string
) : void {
2023-02-28 12:28:20 +00:00
if ( vulnerableChanges . length === 0 ) {
2023-02-27 16:05:59 +00:00
return
}
2022-08-01 21:07:02 +01:00
const rows : SummaryTableRow [] = []
2023-02-28 12:28:20 +00:00
const manifests = getManifestsSet ( vulnerableChanges )
2022-08-01 21:07:02 +01:00
2023-02-28 12:28:20 +00:00
core . summary . addHeading ( 'Vulnerabilities' , 2 )
2022-08-01 21:07:02 +01:00
for ( const manifest of manifests ) {
2023-02-28 12:28:20 +00:00
for ( const change of vulnerableChanges . filter (
2022-08-01 21:07:02 +01:00
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
}
}
2023-02-28 12:28:20 +00:00
core . summary . addHeading ( `<em> ${ manifest } </em>` , 4 ). addTable ([
2022-08-01 21:07:02 +01:00
[
{ data : 'Name' , header : true },
{ data : 'Version' , header : true },
{ data : 'Vulnerability' , header : true },
{ data : 'Severity' , header : true }
],
... rows
])
}
2023-02-27 16:05:59 +00:00
if ( severity !== 'low' ) {
core . summary . addQuote (
`Only included vulnerabilities with severity <strong> ${ severity } </strong> or higher.`
)
}
2022-08-01 21:07:02 +01:00
}
export function addLicensesToSummary (
2023-02-28 11:08:39 +00:00
invalidLicenseChanges : InvalidLicenseChanges ,
2022-08-01 21:07:02 +01:00
config : ConfigurationOptions
) : void {
2023-02-28 11:08:39 +00:00
if ( countLicenseIssues ( invalidLicenseChanges ) === 0 ) {
return
}
2023-02-28 12:28:20 +00:00
core . summary . addHeading ( 'License Issues' , 2 )
2023-03-02 07:43:23 +00:00
printLicenseViolations ( invalidLicenseChanges )
2022-08-01 21:07:02 +01:00
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 ( ', ' ) } `
)
}
2023-03-08 12:38:34 +01:00
if ( config . allow_dependencies_licenses ) {
2023-04-06 10:04:48 +02:00
core . summary . addQuote (
`<strong>Excluded from license check</strong>: ${ config . allow_dependencies_licenses . join (
', '
) } `
)
2023-03-08 12:38:34 +01:00
}
2022-08-01 21:07:02 +01:00
2022-10-27 13:09:37 +00:00
core . debug (
`found ${ invalidLicenseChanges . unlicensed . length } unknown licenses`
)
2022-08-01 21:07:02 +01:00
2022-10-27 16:24:30 +00:00
core . debug (
` ${ invalidLicenseChanges . unresolved . length } licenses could not be validated`
)
}
2022-10-27 16:43:45 +00:00
2023-03-02 07:43:23 +00:00
const licenseIssueTypes : InvalidLicenseChangeTypes [] = [
'forbidden' ,
'unresolved' ,
'unlicensed'
]
2022-08-01 21:07:02 +01:00
2023-03-02 07:43:23 +00:00
const issueTypeNames : Record < InvalidLicenseChangeTypes , string > = {
forbidden : 'Incompatible License' ,
unresolved : 'Invalid SPDX License' ,
unlicensed : 'Unknown License'
}
2022-08-01 21:07:02 +01:00
2023-03-02 07:43:23 +00:00
function printLicenseViolations ( changes : InvalidLicenseChanges ) : void {
const rowsGroupedByManifest : Record < string , SummaryTableRow [] > = {}
2022-08-01 21:07:02 +01:00
2023-03-02 07:43:23 +00:00
for ( const issueType of licenseIssueTypes ) {
for ( const change of changes [ issueType ]) {
if ( ! rowsGroupedByManifest [ change . manifest ]) {
rowsGroupedByManifest [ change . manifest ] = []
}
rowsGroupedByManifest [ change . manifest ]. push ([
2023-02-28 11:08:39 +00:00
renderUrl ( change . source_repository_url , change . name ),
change . version ,
2023-03-02 07:43:23 +00:00
formatLicense ( change . license ),
issueTypeNames [ issueType ]
2023-02-28 11:08:39 +00:00
])
2022-08-01 21:07:02 +01:00
}
2023-03-02 07:43:23 +00:00
}
2023-02-28 11:08:39 +00:00
2023-03-02 07:43:23 +00:00
for ( const [ manifest , rows ] of Object . entries ( rowsGroupedByManifest )) {
core . summary . addHeading ( `<em> ${ manifest } </em>` , 4 )
core . summary . addTable ([
[ 'Package' , 'Version' , 'License' , 'Issue Type' ],
... rows
])
2022-08-01 21:07:02 +01:00
}
}
2022-10-27 16:43:45 +00:00
function formatLicense ( license : string | null ) : string {
if ( license === null || license === 'NOASSERTION' ) {
return 'Null'
}
return license
}
2022-09-26 19:14:04 +02:00
export function addScannedDependencies ( changes : Changes ) : void {
const dependencies = groupDependenciesByManifest ( changes )
const manifests = dependencies . keys ()
2023-02-28 12:28:20 +00:00
const summary = core . summary . addHeading ( 'Scanned Manifest Files' , 2 )
2022-09-26 19:14:04 +02:00
for ( const manifest of manifests ) {
const deps = dependencies . get ( manifest )
if ( deps ) {
const dependencyNames = deps . map (
2022-09-27 11:52:15 +02:00
dependency => `<li> ${ dependency . name } @ ${ dependency . version } </li>`
2022-09-26 19:14:04 +02:00
)
2022-10-27 15:19:32 +00:00
summary . addDetails ( manifest , `<ul> ${ dependencyNames . join ( '' ) } </ul>` )
2022-09-26 19:14:04 +02:00
}
}
}
2023-02-22 14:05:52 +00:00
2023-09-07 17:54:42 +00:00
function snapshotWarningRecommendation (
config : ConfigurationOptions ,
warnings : string
) : string {
const no_pr_snaps = warnings . includes (
'No snapshots were found for the head SHA'
)
const retries_disabled = ! config . retry_on_snapshot_warnings
if ( no_pr_snaps && retries_disabled ) {
2023-09-07 18:00:57 +00:00
return 'Ensure that dependencies are being submitted on PR branches and consider enabling <em>retry-on-snapshot-warnings</em>.'
2023-09-07 17:54:42 +00:00
} else if ( no_pr_snaps ) {
return 'Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue.'
} else if ( retries_disabled ) {
2023-09-07 18:00:57 +00:00
return 'Consider enabling <em>retry-on-snapshot-warnings</em>.'
2023-09-07 17:54:42 +00:00
}
return 'Re-running this action after a short time may resolve the issue.'
}
export function addSnapshotWarnings (
config : ConfigurationOptions ,
warnings : string
) : void {
2023-03-22 21:13:20 +00:00
core . summary . addHeading ( 'Snapshot Warnings' , 2 )
core . summary . addQuote ( ` ${ icons . warning } : ${ warnings } ` )
2023-09-07 17:54:42 +00:00
const recommendation = snapshotWarningRecommendation ( config , warnings )
const docsLink =
'See <a href="https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#best-practices-for-using-the-dependency-review-api-and-the-dependency-submission-api-together">the documentation</a> for more information and troubleshooting advice.'
2023-09-07 18:00:57 +00:00
core . summary . addRaw ( ` ${ recommendation } ${ docsLink } ` )
2023-03-22 21:13:20 +00:00
}
2023-02-22 14:05:52 +00:00
function countLicenseIssues (
invalidLicenseChanges : InvalidLicenseChanges
) : number {
return Object . values ( invalidLicenseChanges ). reduce (
( acc , val ) => acc + val . length ,
0
)
}
2023-08-02 16:17:51 +02:00
export function addDeniedToSummary ( deniedChanges : Change []) : void {
if ( deniedChanges . length === 0 ) {
return
}
core . summary . addHeading ( 'Denied dependencies' , 2 )
for ( const change of deniedChanges ) {
core . summary . addHeading ( `<em>Denied dependencies</em>` , 4 )
core . summary . addTable ([
[ 'Package' , 'Version' , 'License' ],
[
renderUrl ( change . source_repository_url , change . name ),
change . version ,
change . license || ''
]
])
}
}
2023-02-27 16:05:59 +00:00
function checkOrFailIcon ( count : number ) : string {
2023-02-22 14:05:52 +00:00
return count === 0 ? icons.check : icons.cross
}
2023-02-27 16:05:59 +00:00
function checkOrWarnIcon ( count : number ) : string {
2023-02-22 14:05:52 +00:00
return count === 0 ? icons.check : icons.warning
}