diff --git a/action.yaml b/action.yaml index e7ea3405..8bf56822 100644 --- a/action.yaml +++ b/action.yaml @@ -19,15 +19,14 @@ inputs: footer: description: 'A block of Markdown that will be appended to any PR comments posted' required: false - eval-indirect-dependencies: - deprecationMessage: 'Deprecated in favor of SCA configurations' - description: 'Show vulnerabilities found in transitive dependencies' - required: false - default: false artifact-prefix: description: 'Prefix for the artifact name' required: false default: '' + code-scanning-path: + description: 'Path to write code scanning SARIF file' + required: false + default: 'code-scanning.sarif' outputs: old-completed: description: 'If running a target called old, whether the analysis for this was completed' @@ -97,3 +96,4 @@ runs: token: '${{ inputs.token || github.token }}' footer: '${{ inputs.footer }}' artifact-prefix: '${{ inputs.artifact-prefix }}' + code-scanning-path: '${{ inputs.code-scanning-path }}' diff --git a/src/index.ts b/src/index.ts index 00f79bc5..2ff2785a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,15 @@ import { error, getInput, info, setOutput, warning } from '@actions/core' -import { appendFileSync, existsSync } from 'fs' +import { appendFileSync, existsSync, readFileSync } from 'fs' import { downloadArtifact, postCommentIfInPr, resolveExistingCommentIfFound, uploadArtifact, } from './actions' -import { compareResults } from './tool' import { - callCommand, callLaceworkCli, debug, + generateUILink, getActionRef, getMsSinceStart, getOptionalEnvVariable, @@ -21,10 +20,9 @@ import { import path from 'path' -const scaSarifReport = 'scaReport/output.sarif' -const scaReport = 'sca.sarif' -const scaLWJSONReport = 'scaReport/output-lw.json' -const scaDir = 'scaReport' +const artifactPrefix = getInput('artifact-prefix') +const sarifReportPath = getInput('code-scanning-path') +const comparisonMarkdownPath = 'comparison.md' async function runAnalysis() { const target = getInput('target') @@ -40,11 +38,20 @@ async function runAnalysis() { } info('Analyzing ' + target) - telemetryCollector.addField('tools', 'sca') const toUpload: string[] = [] // command to print both sarif and lwjson formats - var args = ['sca', 'scan', '.', '-o', scaDir, '--formats', 'sarif,lw-json', '--deployment', 'ci'] + var args = [ + 'sca', + 'scan', + '.', + '--formats', + 'sarif', + '--output', + sarifReportPath, + '--deployment', + 'ci', + ] if (target === 'push') { args.push('--save-results') } @@ -52,56 +59,70 @@ async function runAnalysis() { args.push('--debug') } await callLaceworkCli(...args) - // make a copy of the sarif file - args = [scaSarifReport, scaReport] - await callCommand('cp', ...args) - - toUpload.push(scaReport) + toUpload.push(sarifReportPath) const uploadStart = Date.now() - const artifactPrefix = getInput('artifact-prefix') - if (artifactPrefix !== '') { - await uploadArtifact(artifactPrefix + '-results-' + target, ...toUpload) - } else { - await uploadArtifact('results-' + target, ...toUpload) - } + + await uploadArtifact(getArtifactName(target), ...toUpload) + telemetryCollector.addField('duration.upload-artifacts', (Date.now() - uploadStart).toString()) setOutput(`${target}-completed`, true) } +export async function compareResults(oldReport: string, newReport: string): Promise { + const args = [ + 'sca', + 'compare', + '--old', + oldReport, + '--new', + newReport, + '--output', + sarifReportPath, + '--markdown', + comparisonMarkdownPath, + '--markdown-variant', + 'GitHub', + '--deployment', + 'ci', + ] + const uiLink = generateUILink() + if (uiLink) args.push(...['--ui-link', uiLink]) + if (debug()) args.push('--debug') + + await callLaceworkCli(...args) + await uploadArtifact(getArtifactName('compare'), sarifReportPath, comparisonMarkdownPath) + + return existsSync(comparisonMarkdownPath) ? readFileSync(comparisonMarkdownPath, 'utf8') : '' +} + async function displayResults() { info('Displaying results') const downloadStart = Date.now() - const artifactOld = await downloadArtifact('results-old') - const artifactNew = await downloadArtifact('results-new') + const artifactOld = await downloadArtifact(getArtifactName('old')) + const artifactNew = await downloadArtifact(getArtifactName('new')) telemetryCollector.addField( 'duration.download-artifacts', (Date.now() - downloadStart).toString() ) - const sarifFileOld = path.join(artifactOld, scaReport) - const sarifFileNew = path.join(artifactNew, scaReport) + const sarifFileOld = path.join(artifactOld, sarifReportPath) + const sarifFileNew = path.join(artifactNew, sarifReportPath) - const issuesByTool: { [tool: string]: string } = {} + var compareMessage: string if (existsSync(sarifFileOld) && existsSync(sarifFileNew)) { - issuesByTool['sca'] = await compareResults('sca', sarifFileOld, sarifFileNew) + compareMessage = await compareResults(sarifFileOld, sarifFileNew) } else { - throw new Error('SARIF file not found for SCA') + throw new Error('SARIF file not found') } const commentStart = Date.now() - if (Object.values(issuesByTool).some((x) => x.length > 0) && getInput('token').length > 0) { + if (compareMessage.length > 0 && getInput('token').length > 0) { info('Posting comment to GitHub PR as there were new issues introduced:') - let message = '' - for (const [, issues] of Object.entries(issuesByTool)) { - if (issues.length > 0) { - message += issues - } - } if (getInput('footer') !== '') { - message += '\n\n' + getInput('footer') + compareMessage += '\n\n' + getInput('footer') } - info(message) - const commentUrl = await postCommentIfInPr(message) + info(compareMessage) + const commentUrl = await postCommentIfInPr(compareMessage) if (commentUrl !== undefined) { setOutput('posted-comment', commentUrl) } @@ -112,6 +133,14 @@ async function displayResults() { setOutput(`display-completed`, true) } +function getArtifactName(target: string): string { + var artifactName = 'results-' + if (artifactPrefix !== '') { + artifactName = artifactPrefix + '-' + artifactName + } + return artifactName + target +} + async function main() { telemetryCollector.addField('duration.install', getMsSinceStart()) telemetryCollector.addField('version', getActionRef()) diff --git a/src/post.ts b/src/post.ts index 9bad798b..df37e386 100644 --- a/src/post.ts +++ b/src/post.ts @@ -1,4 +1,6 @@ import { info, warning } from '@actions/core' +import { context } from '@actions/github' +import { getActionsApi } from './actions' import { getActionRef, getMsSinceStart, @@ -7,8 +9,6 @@ import { getRunUrl, telemetryCollector, } from './util' -import { getActionsApi } from './actions' -import { context } from '@actions/github' async function main() { if (getOptionalEnvVariable('LACEWORK_WROTE_TELEMETRY', 'false') !== 'true') { @@ -32,7 +32,6 @@ async function main() { telemetryCollector.addField('repository', getRequiredEnvVariable('GITHUB_REPOSITORY')) telemetryCollector.addField('duration.total', getMsSinceStart()) telemetryCollector.addField('error', 'Unknown catastrophic error') - telemetryCollector.addField('tools', 'sca') await telemetryCollector.report() } else { info('Telemetry has been reported previously') diff --git a/src/tool.ts b/src/tool.ts deleted file mode 100644 index 81935c53..00000000 --- a/src/tool.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { endGroup, startGroup } from '@actions/core' -import { existsSync, readFileSync } from 'fs' -import { callLaceworkCli, debug, generateUILink } from './util' - -export async function compareResults( - tool: string, - oldReport: string, - newReport: string -): Promise { - startGroup(`Comparing ${tool} results`) - const args = [ - tool, - 'compare', - '--old', - oldReport, - '--new', - newReport, - '--markdown', - `${tool}.md`, - '--markdown-variant', - 'GitHub', - '--deployment', - 'ci', - ] - - const uiLink = generateUILink() - if (uiLink) args.push(...['--ui-link', uiLink]) - - if (debug()) args.push('--debug') - await callLaceworkCli(...args) - endGroup() - return existsSync(`${tool}.md`) ? readFileSync(`${tool}.md`, 'utf8') : '' -} diff --git a/src/util.ts b/src/util.ts index d213c790..9d65dbe6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,8 +1,8 @@ import { error, getInput, info, isDebug } from '@actions/core' import { context } from '@actions/github' import { spawn } from 'child_process' -import { TelemetryCollector } from './telemetry' import { readFileSync } from 'fs' +import { TelemetryCollector } from './telemetry' export const telemetryCollector = new TelemetryCollector()